blob: 42b1b20a6d9833dcacaf08b0469792fe26618561 [file] [log] [blame]
// Copyright 2015 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/api/management/management_api.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/types/optional_ref.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/background/background_contents.h"
#include "chrome/browser/extensions/extension_install_prompt_show_params.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/extension_service_test_with_install.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.h"
#include "chrome/browser/supervised_user/supervised_user_extensions_metrics_recorder.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/supervised_user/supervised_user_test_util.h"
#include "chrome/browser/ui/extensions/extensions_dialogs.h"
#include "components/supervised_user/core/browser/supervised_user_service.h"
#include "components/supervised_user/core/common/features.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "components/supervised_user/core/common/supervised_user_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/api/management/management_api_constants.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/supervised_user_extensions_delegate.h"
#include "extensions/browser/test_management_policy.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/api/management.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/permissions/permission_set.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using extensions::mojom::ManifestLocation;
namespace extensions {
namespace {
constexpr char kUninstallSourceHistogramName[] = "Extensions.UninstallSource";
std::unique_ptr<KeyedService> BuildManagementApi(
content::BrowserContext* context) {
return std::make_unique<ManagementAPI>(context);
}
std::unique_ptr<KeyedService> BuildEventRouter(
content::BrowserContext* profile) {
return std::make_unique<extensions::EventRouter>(
profile, ExtensionPrefs::Get(profile));
}
namespace constants = extension_management_api_constants;
// TODO(devlin): Unittests are awesome. Test more with unittests and less with
// heavy api/browser tests.
class ManagementApiUnitTest : public ExtensionServiceTestWithInstall {
public:
ManagementApiUnitTest(const ManagementApiUnitTest&) = delete;
ManagementApiUnitTest& operator=(const ManagementApiUnitTest&) = delete;
protected:
ManagementApiUnitTest() = default;
~ManagementApiUnitTest() override = default;
// A wrapper around api_test_utils::RunFunction that runs with
// the associated browser, no flags, and can take stack-allocated arguments.
bool RunFunction(const scoped_refptr<ExtensionFunction>& function,
const base::Value::List& args);
// Runs the management.setEnabled() function to enable an extension.
bool RunSetEnabledFunction(content::WebContents* web_contents,
const ExtensionId& extension_id,
bool use_user_gesture,
bool accept_dialog,
std::string* error,
bool enabled = true);
// Returns the initialization parameters for the extension service.
virtual ExtensionServiceInitParams GetExtensionServiceInitParams() {
return ExtensionServiceInitParams();
}
// ExtensionServiceTestBase:
void SetUp() override;
void TearDown() override;
private:
// This test does not create a root window. Because of this,
// ScopedDisableRootChecking needs to be used (which disables the root window
// check).
test::ScopedDisableRootChecking disable_root_checking_;
};
bool ManagementApiUnitTest::RunFunction(
const scoped_refptr<ExtensionFunction>& function,
const base::Value::List& args) {
return api_test_utils::RunFunction(function.get(), args.Clone(), profile(),
api_test_utils::FunctionMode::kNone);
}
bool ManagementApiUnitTest::RunSetEnabledFunction(
content::WebContents* web_contents,
const ExtensionId& extension_id,
bool use_user_gesture,
bool accept_dialog,
std::string* error,
bool enabled) {
ScopedTestDialogAutoConfirm auto_confirm(
accept_dialog ? ScopedTestDialogAutoConfirm::ACCEPT
: ScopedTestDialogAutoConfirm::CANCEL);
std::optional<ExtensionFunction::ScopedUserGestureForTests> gesture =
std::nullopt;
if (use_user_gesture) {
gesture.emplace();
}
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
if (web_contents) {
function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame());
}
base::Value::List args;
args.Append(extension_id);
args.Append(enabled);
bool result = RunFunction(function, args);
if (error) {
*error = function->GetError();
}
return result;
}
void ManagementApiUnitTest::SetUp() {
ExtensionServiceTestBase::SetUp();
InitializeExtensionService(GetExtensionServiceInitParams());
ManagementAPI::GetFactoryInstance()->SetTestingFactory(
profile(), base::BindRepeating(&BuildManagementApi));
EventRouterFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating(&BuildEventRouter));
}
void ManagementApiUnitTest::TearDown() {
// TODO(crbug.com/371332103): Call ExtensionServiceTestWithInstall::TearDown
// here. As-is this skips the ExtensionServiceUserTestBase::TearDown() call
// on Chrome OS. It's unclear if that's important.
ExtensionServiceTestBase::TearDown();
}
// Test the basic parts of management.setEnabled.
TEST_F(ManagementApiUnitTest, ManagementSetEnabled) {
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
scoped_refptr<const Extension> source_extension =
ExtensionBuilder("Test").Build();
registrar()->AddExtension(source_extension.get());
const ExtensionId& extension_id = extension->id();
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
function->set_extension(source_extension);
base::Value::List disable_args;
disable_args.Append(extension_id);
disable_args.Append(false);
// Test disabling an (enabled) extension.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_TRUE(RunFunction(function, disable_args)) << function->GetError();
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
base::Value::List enable_args;
enable_args.Append(extension_id);
enable_args.Append(true);
// Test re-enabling it.
function = base::MakeRefCounted<ManagementSetEnabledFunction>();
EXPECT_TRUE(RunFunction(function, enable_args)) << function->GetError();
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
// Test that the enable function checks management policy, so that we can't
// disable an extension that is required.
TestManagementPolicyProvider provider(
TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS);
ManagementPolicy* policy =
ExtensionSystem::Get(profile())->management_policy();
policy->RegisterProvider(&provider);
function = base::MakeRefCounted<ManagementSetEnabledFunction>();
EXPECT_FALSE(RunFunction(function, disable_args));
EXPECT_EQ(ErrorUtils::FormatErrorMessage(constants::kUserCantModifyError,
extension_id),
function->GetError());
policy->UnregisterProvider(&provider);
}
// chrome.management.setEnabled can be called with or without a user gesture.
// Verify that the associated histogram is set accordingly.
TEST_F(ManagementApiUnitTest, ManagementSetEnabledMaybeHasUserGesture) {
base::HistogramTester histogram_tester;
base::UserActionTester user_action_tester;
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
auto run_set_enabled_function = [&](bool use_user_gesture) {
bool success = RunSetEnabledFunction(
/*web_contents=*/nullptr, extension->id(), use_user_gesture,
/*accept_dialog=*/true, nullptr);
ASSERT_TRUE(success);
histogram_tester.ExpectBucketCount(
ManagementSetEnabledFunction::kSetEnabledHasUserGestureHistogramName,
use_user_gesture, 1);
};
run_set_enabled_function(/*use_user_gesture=*/true);
run_set_enabled_function(/*use_user_gesture=*/false);
}
// Test that component extensions cannot be disabled, and that policy extensions
// can be disabled only by component/policy extensions.
TEST_F(ManagementApiUnitTest, ComponentPolicyDisabling) {
auto component = ExtensionBuilder("component")
.SetLocation(ManifestLocation::kComponent)
.Build();
auto component2 = ExtensionBuilder("component2")
.SetLocation(ManifestLocation::kComponent)
.Build();
auto policy = ExtensionBuilder("policy")
.SetLocation(ManifestLocation::kExternalPolicy)
.Build();
auto policy2 = ExtensionBuilder("policy2")
.SetLocation(ManifestLocation::kExternalPolicy)
.Build();
auto internal = ExtensionBuilder("internal")
.SetLocation(ManifestLocation::kInternal)
.Build();
registrar()->AddExtension(component.get());
registrar()->AddExtension(component2.get());
registrar()->AddExtension(policy.get());
registrar()->AddExtension(policy2.get());
registrar()->AddExtension(internal.get());
auto extension_can_disable_extension =
[this](scoped_refptr<const Extension> source_extension,
scoped_refptr<const Extension> target_extension) {
const ExtensionId& id = target_extension->id();
base::Value::List args;
args.Append(id);
args.Append(false /* disable the extension */);
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
function->set_extension(source_extension);
bool did_disable = RunFunction(function, args);
// If the extension was disabled, re-enable it.
if (did_disable) {
EXPECT_TRUE(registry()->disabled_extensions().Contains(id));
registrar()->EnableExtension(id);
} else {
EXPECT_TRUE(registry()->enabled_extensions().Contains(id));
}
return did_disable;
};
// Component extension cannot be disabled.
EXPECT_FALSE(extension_can_disable_extension(component2, component));
EXPECT_FALSE(extension_can_disable_extension(policy, component));
EXPECT_FALSE(extension_can_disable_extension(internal, component));
// Policy extension can be disabled by component/policy extensions, but not
// others.
EXPECT_TRUE(extension_can_disable_extension(component, policy));
EXPECT_TRUE(extension_can_disable_extension(policy2, policy));
EXPECT_FALSE(extension_can_disable_extension(internal, policy));
}
// Test that policy extensions can be enabled only by component/policy
// extensions.
TEST_F(ManagementApiUnitTest, ComponentPolicyEnabling) {
auto component = ExtensionBuilder("component")
.SetLocation(ManifestLocation::kComponent)
.Build();
auto policy = ExtensionBuilder("policy")
.SetLocation(ManifestLocation::kExternalPolicy)
.Build();
auto policy2 = ExtensionBuilder("policy2")
.SetLocation(ManifestLocation::kExternalPolicy)
.Build();
auto internal = ExtensionBuilder("internal")
.SetLocation(ManifestLocation::kInternal)
.Build();
registrar()->AddExtension(component.get());
registrar()->AddExtension(policy.get());
registrar()->AddExtension(policy2.get());
registrar()->AddExtension(internal.get());
registrar()->DisableExtensionWithSource(
component.get(), policy->id(), disable_reason::DISABLE_BLOCKED_BY_POLICY);
auto extension_can_enable_extension =
[this, component](scoped_refptr<const Extension> source_extension,
scoped_refptr<const Extension> target_extension) {
const ExtensionId& id = target_extension->id();
base::Value::List args;
args.Append(id);
args.Append(true /* enable the extension */);
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
function->set_extension(source_extension);
bool did_enable = RunFunction(function, args);
// If the extension was enabled, disable it.
if (did_enable) {
EXPECT_TRUE(registry()->enabled_extensions().Contains(id));
registrar()->DisableExtensionWithSource(
component.get(), id, disable_reason::DISABLE_BLOCKED_BY_POLICY);
} else {
EXPECT_TRUE(registry()->disabled_extensions().Contains(id));
}
return did_enable;
};
// Policy extension can be enabled by component/policy extensions, but not
// others.
EXPECT_TRUE(extension_can_enable_extension(component, policy));
EXPECT_TRUE(extension_can_enable_extension(policy2, policy));
EXPECT_FALSE(extension_can_enable_extension(internal, policy));
}
// Tests management.uninstall.
TEST_F(ManagementApiUnitTest, ManagementUninstall) {
// Note: uninstall calls must come from an extension, WebUI or the Webstore.
// To test default behavior we test calling from a WebUI context, akin to what
// we would get from the extension management page.
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& extension_id = extension->id();
base::Value::List uninstall_args;
uninstall_args.Append(extension->id());
base::HistogramTester tester;
// Auto-accept any uninstalls.
{
ScopedTestDialogAutoConfirm auto_confirm(
ScopedTestDialogAutoConfirm::ACCEPT);
// Uninstall requires a user gesture, so this should fail.
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_context_type(mojom::ContextType::kWebUi);
EXPECT_FALSE(RunFunction(function, uninstall_args));
EXPECT_EQ(std::string(constants::kGestureNeededForUninstallError),
function->GetError());
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_context_type(mojom::ContextType::kWebUi);
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
// The extension should be uninstalled.
EXPECT_FALSE(registry()->GetExtensionById(extension_id,
ExtensionRegistry::EVERYTHING));
tester.ExpectBucketCount(
kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_EXTENSIONS_PAGE, 1);
}
// Install the extension again, and try uninstalling, auto-canceling the
// dialog.
{
ScopedTestDialogAutoConfirm auto_confirm(
ScopedTestDialogAutoConfirm::CANCEL);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
registrar()->AddExtension(extension.get());
scoped_refptr<ExtensionFunction> function =
base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_context_type(mojom::ContextType::kWebUi);
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_FALSE(RunFunction(function, uninstall_args));
// The uninstall should have failed.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_EQ(ErrorUtils::FormatErrorMessage(constants::kUninstallCanceledError,
extension_id),
function->GetError());
tester.ExpectBucketCount(
kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_EXTENSIONS_PAGE, 2);
// Try again, using showConfirmDialog: false.
base::Value::Dict options;
options.Set("showConfirmDialog", false);
uninstall_args.Append(std::move(options));
function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_context_type(mojom::ContextType::kWebUi);
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_FALSE(RunFunction(function, uninstall_args));
// This should still fail, since extensions can only suppress the dialog for
// uninstalling themselves.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_EQ(ErrorUtils::FormatErrorMessage(constants::kUninstallCanceledError,
extension_id),
function->GetError());
tester.ExpectBucketCount(
kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_EXTENSIONS_PAGE, 3);
// If we have the extension uninstall itself, the uninstall should succeed
// (even though we auto-cancel any dialog), because the dialog is never
// shown.
uninstall_args.erase(uninstall_args.begin());
function = base::MakeRefCounted<ManagementUninstallSelfFunction>();
// Note: this time the source is coming from the extension itself, not a
// WebUI based context.
function->set_extension(extension);
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
EXPECT_FALSE(registry()->GetExtensionById(extension_id,
ExtensionRegistry::EVERYTHING));
// Note: No Extensins.UninstallSource bucket is incremented here, as no
// dialog was shown.
}
}
// Tests management.uninstall from the Web Store hosted app.
TEST_F(ManagementApiUnitTest, ManagementUninstallWebstoreHostedApp) {
scoped_refptr<const Extension> triggering_extension =
ExtensionBuilder("Test").SetID(extensions::kWebStoreAppId).Build();
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& extension_id = extension->id();
base::Value::List uninstall_args;
uninstall_args.Append(extension->id());
base::HistogramTester tester;
{
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_extension(triggering_extension);
ScopedTestDialogAutoConfirm auto_confirm(
ScopedTestDialogAutoConfirm::CANCEL);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_FALSE(RunFunction(function, uninstall_args));
// When the dialog is automatically canceled, an error will have been
// reported to the extension telling it.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_EQ(ErrorUtils::FormatErrorMessage(constants::kUninstallCanceledError,
extension_id),
function->GetError());
tester.ExpectBucketCount(kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_WEBSTORE, 1);
}
{
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_extension(triggering_extension);
bool did_show = false;
auto callback = base::BindRepeating(
[](bool* did_show) { *did_show = true; }, &did_show);
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(
&callback);
ScopedTestDialogAutoConfirm auto_confirm(
ScopedTestDialogAutoConfirm::ACCEPT);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
// The extension should be uninstalled.
EXPECT_EQ(nullptr, registry()->GetInstalledExtension(extension_id));
EXPECT_TRUE(did_show);
tester.ExpectBucketCount(kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_WEBSTORE, 2);
// Reset the callback.
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(nullptr);
}
}
// Tests management.uninstall from the new Webstore domain.
TEST_F(ManagementApiUnitTest, ManagementUninstallNewWebstore) {
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& extension_id = extension->id();
base::Value::List uninstall_args;
uninstall_args.Append(extension->id());
base::HistogramTester tester;
// Note: no triggering extension is set on the ExtensionFunction, but the
// associated URL should be from the webstore domain.
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_url(GURL(extension_urls::GetNewWebstoreLaunchURL()));
bool did_show = false;
auto callback =
base::BindRepeating([](bool* did_show) { *did_show = true; }, &did_show);
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(&callback);
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
// The extension should be uninstalled.
EXPECT_EQ(nullptr, registry()->GetInstalledExtension(extension_id));
EXPECT_TRUE(did_show);
tester.ExpectBucketCount(kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_CHROME_WEBSTORE, 1);
// Reset the callback.
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(nullptr);
}
// Tests management.uninstall from a normal extension, which will create a
// programmatic uninstall dialog that identifies the extension that called it.
TEST_F(ManagementApiUnitTest, ManagementUninstallProgramatic) {
scoped_refptr<const Extension> triggering_extension =
ExtensionBuilder("Triggering Extension").SetID("123").Build();
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& extension_id = extension->id();
base::Value::List uninstall_args;
uninstall_args.Append(extension->id());
base::HistogramTester tester;
{
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_extension(triggering_extension);
bool did_show = false;
auto callback = base::BindRepeating(
[](bool* did_show) { *did_show = true; }, &did_show);
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(
&callback);
ScopedTestDialogAutoConfirm auto_confirm(
ScopedTestDialogAutoConfirm::ACCEPT);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
// The extension should be uninstalled.
EXPECT_EQ(nullptr, registry()->GetInstalledExtension(extension_id));
EXPECT_TRUE(did_show);
tester.ExpectBucketCount(kUninstallSourceHistogramName,
extensions::UNINSTALL_SOURCE_EXTENSION, 1);
// Reset the callback.
extensions::ExtensionUninstallDialog::SetOnShownCallbackForTesting(nullptr);
}
}
// Tests uninstalling a blocklisted extension via management.uninstall.
TEST_F(ManagementApiUnitTest, ManagementUninstallBlocklisted) {
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& id = extension->id();
service()->BlocklistExtensionForTest(id);
EXPECT_NE(nullptr, registry()->GetInstalledExtension(id));
ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
auto function = base::MakeRefCounted<ManagementUninstallFunction>();
function->set_source_context_type(mojom::ContextType::kWebUi);
base::Value::List uninstall_args;
uninstall_args.Append(id);
EXPECT_TRUE(RunFunction(function, uninstall_args)) << function->GetError();
EXPECT_EQ(nullptr, registry()->GetInstalledExtension(id));
}
TEST_F(ManagementApiUnitTest, ManagementEnableOrDisableBlocklisted) {
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const ExtensionId& id = extension->id();
service()->BlocklistExtensionForTest(id);
EXPECT_NE(nullptr, registry()->GetInstalledExtension(id));
// Test enabling it.
{
base::Value::List enable_args;
enable_args.Append(id);
enable_args.Append(true);
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
EXPECT_TRUE(RunFunction(function, enable_args)) << function->GetError();
EXPECT_FALSE(registry()->enabled_extensions().Contains(id));
EXPECT_FALSE(registry()->disabled_extensions().Contains(id));
}
// Test disabling it
{
base::Value::List disable_args;
disable_args.Append(id);
disable_args.Append(false);
auto function = base::MakeRefCounted<ManagementSetEnabledFunction>();
EXPECT_TRUE(RunFunction(function, disable_args)) << function->GetError();
EXPECT_FALSE(registry()->enabled_extensions().Contains(id));
EXPECT_FALSE(registry()->disabled_extensions().Contains(id));
}
}
TEST_F(ManagementApiUnitTest, ExtensionInfo_MayEnable) {
using ExtensionInfo = api::management::ExtensionInfo;
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const std::string args =
base::StringPrintf("[\"%s\"]", extension->id().c_str());
// Initially the extension should show as enabled.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
{
auto function = base::MakeRefCounted<ManagementGetFunction>();
std::optional<base::Value> value =
api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
profile());
ASSERT_TRUE(value);
std::optional<ExtensionInfo> info = ExtensionInfo::FromValue(*value);
ASSERT_TRUE(info);
EXPECT_TRUE(info->enabled);
// |may_enable| is only returned for extensions which are not enabled.
EXPECT_FALSE(info->may_enable);
}
// Simulate blocklisting the extension and verify that the extension shows as
// disabled with a false value of |may_enable|.
ManagementPolicy* policy =
ExtensionSystem::Get(profile())->management_policy();
policy->UnregisterAllProviders();
TestManagementPolicyProvider provider(
TestManagementPolicyProvider::PROHIBIT_LOAD);
policy->RegisterProvider(&provider);
service()->CheckManagementPolicy();
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
{
auto function = base::MakeRefCounted<ManagementGetFunction>();
std::optional<base::Value> value =
api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
profile());
ASSERT_TRUE(value);
std::optional<ExtensionInfo> info = ExtensionInfo::FromValue(*value);
ASSERT_TRUE(info);
EXPECT_FALSE(info->enabled);
ASSERT_TRUE(info->may_enable);
EXPECT_FALSE(*(info->may_enable));
}
// Re-enable the extension.
policy->UnregisterAllProviders();
service()->CheckManagementPolicy();
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
// Disable the extension with a normal user action. Verify the extension shows
// as disabled with |may_enable| as true.
registrar()->DisableExtension(extension->id(),
{disable_reason::DISABLE_USER_ACTION});
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
{
auto function = base::MakeRefCounted<ManagementGetFunction>();
std::optional<base::Value> value =
api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
profile());
ASSERT_TRUE(value);
std::optional<ExtensionInfo> info = ExtensionInfo::FromValue(*value);
ASSERT_TRUE(info);
EXPECT_FALSE(info->enabled);
ASSERT_TRUE(info->may_enable);
EXPECT_TRUE(*(info->may_enable));
}
}
TEST_F(ManagementApiUnitTest, ExtensionInfo_MayDisable) {
using ExtensionInfo = api::management::ExtensionInfo;
scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension.get());
const std::string args =
base::StringPrintf("[\"%s\"]", extension->id().c_str());
// Initially the extension should show as enabled, so it may be disabled
// freely.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
{
auto function = base::MakeRefCounted<ManagementGetFunction>();
std::optional<base::Value> value =
api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
profile());
ASSERT_TRUE(value);
std::optional<ExtensionInfo> info = ExtensionInfo::FromValue(*value);
ASSERT_TRUE(info);
EXPECT_TRUE(info->enabled);
EXPECT_TRUE(info->may_disable);
}
// Simulate forcing the extension and verify that the extension shows with
// a false value of |may_disable|.
ManagementPolicy* policy =
ExtensionSystem::Get(profile())->management_policy();
policy->UnregisterAllProviders();
TestManagementPolicyProvider provider(
TestManagementPolicyProvider::MUST_REMAIN_ENABLED);
policy->RegisterProvider(&provider);
service()->CheckManagementPolicy();
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
{
auto function = base::MakeRefCounted<ManagementGetFunction>();
std::optional<base::Value> value =
api_test_utils::RunFunctionAndReturnSingleResult(function.get(), args,
profile());
ASSERT_TRUE(value);
std::optional<ExtensionInfo> info = ExtensionInfo::FromValue(*value);
ASSERT_TRUE(info);
EXPECT_TRUE(info->enabled);
EXPECT_FALSE(info->may_disable);
}
}
TEST_F(ManagementApiUnitTest, SetEnabled_UnsupportedRequirements) {
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
// Install an extension with unsupported requirements.
base::FilePath base_path = data_dir().AppendASCII("requirements");
base::FilePath pem_path = base_path.AppendASCII("v1_good.pem");
base::FilePath path = base_path.AppendASCII("v2_bad_requirements");
// No WebGL will be the unsupported requirement.
content::GpuDataManager::GetInstance()->BlocklistWebGLForTesting();
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension->id(),
false /* use_user_gesture */,
true /* accept_dialog */, &error);
EXPECT_FALSE(success);
EXPECT_EQ(error, "There were missing requirements: WebGL is not supported.");
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension->id()));
}
// Tests enabling an extension via management API after it was disabled due to
// permission increase.
TEST_F(ManagementApiUnitTest, SetEnabled_IncreasedPermissions) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension = PackAndInstallCRX(path, pem_path, INSTALL_NEW);
// The extension must now be installed and enabled.
ASSERT_TRUE(extension);
ASSERT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
// Save the id, as |extension| will be destroyed during updating.
ExtensionId extension_id = extension->id();
std::unique_ptr<const PermissionSet> known_perms =
prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
// v1 extension doesn't have any permissions.
EXPECT_TRUE(known_perms->IsEmpty());
// Update to a new version with increased permissions.
path = base_path.AppendASCII("v2");
PackCRXAndUpdateExtension(extension_id, path, pem_path, DISABLED);
// The extension should be disabled.
ASSERT_FALSE(registry()->enabled_extensions().Contains(extension_id));
// Due to a permission increase, prefs will contain escalation information.
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
// 1) Confirm re-enable prompt without user gesture, expect the extension to
// stay disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
false /* use_user_gesture */,
true /* accept_dialog */, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
// Prefs should still contain permissions escalation information.
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
}
// 2) Deny re-enable prompt without user gesture, expect the extension to stay
// disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
false /* use_user_gesture */,
false /* accept_dialog */, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
// Prefs should still contain permissions escalation information.
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
}
// 3) Deny re-enable prompt with user gesture, expect the extension to stay
// disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
true /* use_user_gesture */,
false /* accept_dialog */, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
// Prefs should still contain permissions escalation information.
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
}
// 4) Accept re-enable prompt with user gesture, expect the extension to be
// enabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
true /* use_user_gesture */,
true /* accept_dialog */, &error);
EXPECT_TRUE(success) << error;
EXPECT_TRUE(error.empty());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
// Prefs will no longer contain the escalation information as user has
// accepted increased permissions.
EXPECT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
}
// Some permissions for v2 extension should be granted by now.
known_perms = prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
EXPECT_FALSE(known_perms->IsEmpty());
}
TEST_F(ManagementApiUnitTest,
SetEnabled_UnsupportedRequirementsAndPermissionsIncrease) {
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
// Install an extension with unsupported requirements and permissions
// increase.
base::FilePath base_path = data_dir().AppendASCII("requirements");
base::FilePath pem_path = base_path.AppendASCII("v1_good.pem");
base::FilePath path =
base_path.AppendASCII("v2_bad_requirements_and_permissions");
// No WebGL will be the unsupported requirement.
content::GpuDataManager::GetInstance()->BlocklistWebGLForTesting();
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
// Unsupported requirements should fail first.
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension->id(),
false /* use_user_gesture */,
true /* accept_dialog */, &error);
EXPECT_FALSE(success);
EXPECT_EQ(error, "There were missing requirements: WebGL is not supported.");
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension->id()));
}
#if !BUILDFLAG(IS_ANDROID)
// Test suite for cases where the user is in the "disable with re-enable"
// experiment phase. Tests are not run on Android, which only supports MV3 and
// hence has no "MV2 re-enable" phase.
class ManagementApiUnitTestMV2DisableWithReEnableUnitTest
: public ManagementApiUnitTest {
public:
ManagementApiUnitTestMV2DisableWithReEnableUnitTest() {
feature_list_.InitWithFeatures(
{extensions_features::kExtensionManifestV2Disabled},
{extensions_features::kExtensionManifestV2Unsupported});
}
~ManagementApiUnitTestMV2DisableWithReEnableUnitTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests the extension is enabled when management.setEnabled is called for
// enabling an extension disabled due to the MV2 deprecation, and user accepted
// the dialog.
TEST_F(ManagementApiUnitTestMV2DisableWithReEnableUnitTest,
SetEnabled_MV2Deprecation) {
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
// Install an extension and disable it due to the MV2 deprecation.
scoped_refptr<const Extension> extension =
ExtensionBuilder("Test").SetManifestVersion(2).Build();
const ExtensionId& extension_id = extension->id();
registrar()->AddExtension(extension.get());
registrar()->DisableExtension(
extension_id, {disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION});
// 1) Deny re-enable prompt without user gesture, expect the extension to
// stay disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
/*use_user_gesture=*/false,
/*accept_dialog=*/false, &error);
EXPECT_FALSE(success);
EXPECT_EQ(error,
"Re-enabling an extension disabled due to MV2 deprecation "
"requires a user gesture.");
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
}
// 2) Deny re-enable prompt with user gesture, expect the extension to
// stay disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/false, &error);
EXPECT_FALSE(success);
EXPECT_EQ(error, "The user did not accept the re-enable dialog.");
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
}
// 3) Accept re-enable prompt without user gesture, expect the extension to
// stay disabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
/*use_user_gesture=*/false,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_EQ(error,
"Re-enabling an extension disabled due to MV2 deprecation "
"requires a user gesture.");
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
}
// 4) Accept re-enable prompt with user gesture, expect the extension to
// be enabled.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success);
EXPECT_TRUE(error.empty());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
}
}
// Tests the extension is enabled when management.setEnabled is called for
// enabling an extension disabled due to the MV2 deprecation and with
// permissions increase, and user accepted both dialogs shown.
TEST_F(ManagementApiUnitTestMV2DisableWithReEnableUnitTest,
SetEnabled_PermissionsIncreaseAndMV2Deprecation) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension = PackAndInstallCRX(path, pem_path, INSTALL_NEW);
// Save the id, as `extension` will be destroyed during updating.
ExtensionId extension_id = extension->id();
// Update extension to a new version with increased permissions.
path = base_path.AppendASCII("v2");
PackCRXAndUpdateExtension(extension_id, path, pem_path, DISABLED);
EXPECT_FALSE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
// Disable extension due to MV2 deprecation. Since extension is already
// disabled, this will add another disable reason.
registrar()->DisableExtension(
extension_id, {disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION});
// management.setEnabled will trigger two dialogs (permissions increase and
// mv2 deprecation). Since we have tested each individually, this test
// only verifies extension is enabled when both dialogs are accepted.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success);
EXPECT_TRUE(error.empty());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
}
}
#endif // !BUILDFLAG(IS_ANDROID)
// A delegate that senses when extensions are enabled or disabled.
class TestManagementAPIDelegate : public ManagementAPIDelegate {
public:
TestManagementAPIDelegate() = default;
~TestManagementAPIDelegate() override = default;
bool LaunchAppFunctionDelegate(
const Extension* extension,
content::BrowserContext* context) const override {
return false;
}
GURL GetFullLaunchURL(const Extension* extension) const override {
return GURL();
}
LaunchType GetLaunchType(const ExtensionPrefs* prefs,
const Extension* extension) const override {
return LaunchType::LAUNCH_TYPE_DEFAULT;
}
std::unique_ptr<InstallPromptDelegate> SetEnabledFunctionDelegate(
content::WebContents* web_contents,
content::BrowserContext* browser_context,
const Extension* extension,
base::OnceCallback<void(bool)> callback) const override {
return nullptr;
}
void EnableExtension(content::BrowserContext* context,
const ExtensionId& extension_id) const override {
++enable_count_;
}
void DisableExtension(
content::BrowserContext* context,
const Extension* source_extension,
const ExtensionId& extension_id,
disable_reason::DisableReason disable_reason) const override {}
std::unique_ptr<UninstallDialogDelegate> UninstallFunctionDelegate(
ManagementUninstallFunctionBase* function,
const Extension* target_extension,
bool show_programmatic_uninstall_ui) const override {
return nullptr;
}
bool UninstallExtension(content::BrowserContext* context,
const ExtensionId& transient_extension_id,
UninstallReason reason,
std::u16string* error) const override {
return true;
}
bool CreateAppShortcutFunctionDelegate(
ManagementCreateAppShortcutFunction* function,
const Extension* extension,
std::string* error) const override {
return true;
}
void SetLaunchType(content::BrowserContext* context,
const ExtensionId& extension_id,
LaunchType launch_type) const override {}
std::unique_ptr<AppForLinkDelegate> GenerateAppForLinkFunctionDelegate(
ManagementGenerateAppForLinkFunction* function,
content::BrowserContext* context,
const std::string& title,
const GURL& launch_url) const override {
return nullptr;
}
bool CanContextInstallWebApps(
content::BrowserContext* context) const override {
return true;
}
void InstallOrLaunchReplacementWebApp(
content::BrowserContext* context,
const GURL& web_app_url,
InstallOrLaunchWebAppCallback callback) const override {}
GURL GetIconURL(const Extension* extension,
int icon_size,
ExtensionIconSet::Match match,
bool grayscale) const override {
return GURL();
}
GURL GetEffectiveUpdateURL(const extensions::Extension& extension,
content::BrowserContext* context) const override {
return GURL();
}
void ShowMv2DeprecationReEnableDialog(
content::BrowserContext* context,
content::WebContents* web_contents,
const extensions::Extension& extension,
base::OnceCallback<void(bool)> done_callback) const override {}
// EnableExtension is const, so this is mutable.
mutable int enable_count_ = 0;
};
// A delegate that allows a child to try to install an extension and tracks
// whether the parent permission dialog would have opened.
class TestSupervisedUserExtensionsDelegate
: public SupervisedUserExtensionsDelegateImpl {
public:
explicit TestSupervisedUserExtensionsDelegate(
content::BrowserContext* context)
: SupervisedUserExtensionsDelegateImpl(context) {}
~TestSupervisedUserExtensionsDelegate() override = default;
// SupervisedUserExtensionsDelegate:
bool IsChild() const override { return true; }
void RequestToAddExtensionOrShowError(
const extensions::Extension& extension,
content::WebContents* contents,
const gfx::ImageSkia& icon,
ExtensionApprovalDoneCallback extension_approval_callback) override {
// Preconditions.
DCHECK(IsChild());
DCHECK(!IsExtensionAllowedByParent(extension));
if (CanInstallExtensions()) {
ShowParentPermissionDialogForExtension(
extension, contents, std::move(extension_approval_callback), icon);
} else {
ShowInstallBlockedByParentDialogForExtension(
extension, contents,
ExtensionInstalledBlockedByParentDialogAction::kAdd,
base::BindOnce(std::move(extension_approval_callback),
SupervisedUserExtensionsDelegate::
ExtensionApprovalResult::kBlocked));
}
}
void RequestToEnableExtensionOrShowError(
const extensions::Extension& extension,
content::WebContents* contents,
ExtensionApprovalDoneCallback extension_approval_callback) override {
// Preconditions.
DCHECK(IsChild());
DCHECK(!IsExtensionAllowedByParent(extension));
if (CanInstallExtensions()) {
ShowParentPermissionDialogForExtension(
extension, contents, std::move(extension_approval_callback),
gfx::ImageSkia());
} else {
ShowInstallBlockedByParentDialogForExtension(
extension, contents,
ExtensionInstalledBlockedByParentDialogAction::kEnable,
base::BindOnce(std::move(extension_approval_callback),
SupervisedUserExtensionsDelegate::
ExtensionApprovalResult::kBlocked));
}
}
void set_next_parent_permission_dialog_result(
ExtensionApprovalResult result) {
dialog_result_ = result;
}
int show_dialog_count() const { return show_dialog_count_; }
int show_block_dialog_count() const { return show_block_dialog_count_; }
private:
// Shows a parent permission dialog for |extension| and call |done_callback|
// when it completes.
void ShowParentPermissionDialogForExtension(
const extensions::Extension& extension,
content::WebContents* contents,
extensions::SupervisedUserExtensionsDelegate::
ExtensionApprovalDoneCallback done_callback,
const gfx::ImageSkia& icon) {
++show_dialog_count_;
std::move(done_callback).Run(dialog_result_);
}
// Shows a dialog indicating that |extension| has been blocked and call
// |done_callback| when it completes.
void ShowInstallBlockedByParentDialogForExtension(
const extensions::Extension& extension,
content::WebContents* contents,
ExtensionInstalledBlockedByParentDialogAction blocked_action,
base::OnceClosure done_callback) {
show_block_dialog_count_++;
std::move(done_callback).Run();
}
ExtensionApprovalResult dialog_result_ = ExtensionApprovalResult::kFailed;
int show_dialog_count_ = 0;
int show_block_dialog_count_ = 0;
};
// Tests for supervised users (child accounts). Supervised users are not allowed
// to install apps or extensions unless their parent approves.
class ManagementApiSupervisedUserTest : public ManagementApiUnitTest {
public:
ManagementApiSupervisedUserTest() = default;
~ManagementApiSupervisedUserTest() override = default;
// ManagementApiUnitTest:
ExtensionServiceInitParams GetExtensionServiceInitParams() override {
ExtensionServiceInitParams params;
params.profile_is_supervised = true;
return params;
}
supervised_user::SupervisedUserService* GetSupervisedUserService() {
return SupervisedUserServiceFactory::GetForProfile(profile());
}
SupervisedUserExtensionsDelegate* GetSupervisedUserExtensionsDelegate() {
return supervised_user_delegate_;
}
void SetUp() override {
ManagementApiUnitTest::SetUp();
// Set up custodians (parents) for the child.
supervised_user_test_util::AddCustodians(profile());
// Set the pref to allow the child to request extension install.
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), true);
// Create a WebContents to simulate the Chrome Web Store.
web_contents_ =
content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
management_api_ = ManagementAPI::GetFactoryInstance()->Get(profile());
// Install a SupervisedUserExtensionsDelegate to sense the dialog state.
supervised_user_delegate_ =
new TestSupervisedUserExtensionsDelegate(profile());
management_api_->set_supervised_user_extensions_delegate_for_test(
base::WrapUnique(supervised_user_delegate_.get()));
}
std::unique_ptr<content::WebContents> web_contents_;
raw_ptr<ManagementAPI> management_api_ = nullptr;
raw_ptr<TestSupervisedUserExtensionsDelegate> supervised_user_delegate_ =
nullptr;
base::test::ScopedFeatureList feature_list_;
};
// Tests that locally approved extensions (when parental extensions control
// apply for the first time) can be enabled by the supervised user.
// The enabling action does not grant parental approval.
// Prevents regressions to crbug.com/336759592.
TEST_F(ManagementApiSupervisedUserTest,
SetEnabled_SetEnabledForLocallyApprovedExtension) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
base::HistogramTester histogram_tester;
// Install an extension.
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
const ExtensionId& extension_id = extension->id();
bool is_locally_parent_approved = false;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
// Simulate a local approval grant for this extension.
base::Value::Dict locally_approved;
locally_approved.Set(extension_id, true);
profile()->GetPrefs()->SetDict(
prefs::kSupervisedUserLocallyParentApprovedExtensions,
std::move(locally_approved));
is_locally_parent_approved = true;
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
// Start with an initially disabled extension.
ASSERT_TRUE(registry()->disabled_extensions().Contains(extension_id));
ASSERT_EQ(supervised_user_delegate_->IsExtensionAllowedByParent(*extension),
is_locally_parent_approved);
// Try to enable it. If the extension is locally approved (Win/Linux/Mac),
// the enabling should succeed. Otherwise it should fail due to missing parent
// approval.
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_EQ(success, is_locally_parent_approved);
EXPECT_EQ(error.empty(), is_locally_parent_approved);
EXPECT_EQ(registry()->enabled_extensions().Contains(extension_id),
is_locally_parent_approved);
EXPECT_EQ(
ExtensionPrefs::Get(profile())->HasDisableReason(
extension_id, disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED),
!is_locally_parent_approved);
int expected_enabled_count = is_locally_parent_approved ? 1 : 0;
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName,
SupervisedUserExtensionsMetricsRecorder::EnablementState::kEnabled,
expected_enabled_count);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName,
expected_enabled_count);
// The enabling of the extension did not affect its parent approval state on
// Preferences level. The extensions should not have parent approval granted,
// even if it's locally approved on the present device.
EXPECT_FALSE(profile()
->GetPrefs()
->GetDict(prefs::kSupervisedUserApprovedExtensions)
.contains(extension_id));
}
// Tests enabling an extension via management API after it was disabled due to
// permission increase for supervised users.
// Prevents a regression to crbug/1068660.
TEST_F(ManagementApiSupervisedUserTest, SetEnabled_AfterIncreasedPermissions) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
base::HistogramTester histogram_tester;
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
// The extension should be installed but disabled pending custodian approval.
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
// Save the id, as |extension| will be destroyed during updating.
const ExtensionId extension_id = extension->id();
// Simulate parent approval for the extension installation.
GetSupervisedUserExtensionsDelegate()->AddExtensionApproval(*extension);
// The extension should be enabled now.
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
// Should see 1 kApprovalGranted UMA metric.
histogram_tester.ExpectUniqueSample(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 1);
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
std::unique_ptr<const PermissionSet> known_perms =
prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
// v1 extension doesn't have any permissions.
EXPECT_TRUE(known_perms->IsEmpty());
// Update to a new version with increased permissions.
path = base_path.AppendASCII("v2");
PackCRXAndUpdateExtension(extension_id, path, pem_path, DISABLED);
// The extension should be disabled.
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
// Due to a permission increase, prefs will contain escalation information.
EXPECT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
// Accept re-enable prompt with user gesture, expect the extension to be
// enabled.
{
// The supervised user will approve the additional permissions without
// parent approval.
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success) << error;
EXPECT_TRUE(error.empty());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
// Prefs will no longer contain the escalation information as the supervised
// user has accepted the increased permissions.
EXPECT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
}
// Permissions for v2 extension should be granted now.
known_perms = prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
EXPECT_FALSE(known_perms->IsEmpty());
// The parent approval dialog should have not appeared.
EXPECT_EQ(0, supervised_user_delegate_->show_dialog_count());
// Should see 1 kPermissionsIncreaseGranted UMA metric.
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kPermissionsIncreaseGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 2);
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests that if an extension still requires parental consent, the supervised
// user approving it for permissions increase won't enable the extension and
// bypass parental consent.
// Prevents a regression to crbug/1070760.
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTest,
SetEnabled_CustodianApprovalRequiredAndPermissionsIncrease) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
// The extension should be installed but disabled pending custodian approval.
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
// Save the id, as |extension| will be destroyed during updating.
const ExtensionId extension_id = extension->id();
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
std::unique_ptr<const PermissionSet> known_perms =
prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
// v1 extension doesn't have any permissions.
EXPECT_TRUE(known_perms->IsEmpty());
// Update to a new version with increased permissions.
path = base_path.AppendASCII("v2");
PackCRXAndUpdateExtension(extension_id, path, pem_path, DISABLED);
// The extension should still be disabled.
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
// This extension has two concurrent disable reasons.
EXPECT_TRUE(prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_PERMISSIONS_INCREASE));
EXPECT_TRUE(prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
// The supervised user trying to enable without parent approval should fail.
{
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
// Both disable reasons should still be present.
EXPECT_TRUE(prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_PERMISSIONS_INCREASE));
EXPECT_TRUE(prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
}
// Permissions for v2 extension should not be granted.
known_perms = prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
EXPECT_TRUE(known_perms->IsEmpty());
// The parent approval dialog should have appeared.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
// Now try again with parent approval, and this should succeed.
{
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kApproved);
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_id,
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success) << error;
EXPECT_TRUE(error.empty());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
// All disable reasons are gone.
EXPECT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
EXPECT_FALSE(prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
}
// Permissions for v2 extension should now be granted.
known_perms = prefs->GetGrantedPermissions(extension_id);
ASSERT_TRUE(known_perms);
EXPECT_FALSE(known_perms->IsEmpty());
// The parent approval dialog should have appeared again.
EXPECT_EQ(2, supervised_user_delegate_->show_dialog_count());
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
// Tests that trying to enable an extension with parent approval for supervised
// users still fails, if there's unsupported requirements.
TEST_F(ManagementApiSupervisedUserTest, SetEnabled_UnsupportedRequirement) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
ASSERT_EQ(0, supervised_user_delegate_->show_dialog_count());
// No WebGL will be the unsupported requirement.
content::GpuDataManager::GetInstance()->BlocklistWebGLForTesting();
base::FilePath base_path = data_dir().AppendASCII("requirements");
base::FilePath pem_path = base_path.AppendASCII("v1_good.pem");
base::FilePath path = base_path.AppendASCII("v2_bad_requirements");
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
// The extension should be installed but disabled pending custodian approval
// and unsupported requirements.
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
EXPECT_TRUE(prefs->HasDisableReason(
extension->id(), disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
EXPECT_TRUE(prefs->HasDisableReason(
extension->id(), disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT));
// Parent approval should fail because of the unsupported requirements.
{
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kApproved);
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension->id(),
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
// The parent permission dialog was never opened.
EXPECT_EQ(0, supervised_user_delegate_->show_dialog_count());
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
// The extension should still require parent approval.
EXPECT_TRUE(prefs->HasDisableReason(
extension->id(), disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
EXPECT_TRUE(prefs->HasDisableReason(
extension->id(), disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT));
}
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests UMA metrics related to supervised users enabling and disabling
// extensions.
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTest, SetEnabledDisabled_UmaMetrics) {
base::HistogramTester histogram_tester;
base::UserActionTester user_action_tester;
base::FilePath base_path = data_dir().AppendASCII("permissions_increase");
base::FilePath pem_path = base_path.AppendASCII("permissions.pem");
base::FilePath path = base_path.AppendASCII("v1");
const Extension* extension =
PackAndInstallCRX(path, pem_path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
// The parent will approve.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kApproved);
RunSetEnabledFunction(web_contents_.get(), extension->id(),
/*use_user_gesture=*/true, /*accept_dialog=*/true,
nullptr, /*enabled=*/true);
histogram_tester.ExpectUniqueSample(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName,
SupervisedUserExtensionsMetricsRecorder::EnablementState::kEnabled, 1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName, 1);
EXPECT_EQ(1,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kEnabledActionName));
EXPECT_EQ(0,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kDisabledActionName));
// Simulate supervised user disabling extension.
RunSetEnabledFunction(web_contents_.get(), extension->id(),
/*use_user_gesture=*/true, /*accept_dialog=*/true,
nullptr, /*enabled=*/false);
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName,
SupervisedUserExtensionsMetricsRecorder::EnablementState::kDisabled, 1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName, 2);
EXPECT_EQ(1,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kEnabledActionName));
EXPECT_EQ(1,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kDisabledActionName));
// Simulate supervised user re-enabling extension.
RunSetEnabledFunction(web_contents_.get(), extension->id(),
/*use_user_gesture=*/true, /*accept_dialog=*/true,
nullptr, /*enabled=*/true);
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName,
SupervisedUserExtensionsMetricsRecorder::EnablementState::kEnabled, 2);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kEnablementHistogramName, 3);
EXPECT_EQ(2,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kEnabledActionName));
EXPECT_EQ(1,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kDisabledActionName));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
// Tests for supervised users (child accounts) with additional setup code.
class ManagementApiSupervisedUserTestWithSetup
: public ManagementApiSupervisedUserTest {
public:
ManagementApiSupervisedUserTestWithSetup() = default;
~ManagementApiSupervisedUserTestWithSetup() override = default;
void SetUp() override {
ManagementApiSupervisedUserTest::SetUp();
// Install a ManagementAPIDelegate to sense extension enable.
delegate_ = new TestManagementAPIDelegate;
management_api_->set_delegate_for_test(base::WrapUnique(delegate_.get()));
// Add a generic extension.
extension_ = ExtensionBuilder("Test").Build();
registrar()->AddExtension(extension_.get());
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_->id()));
}
raw_ptr<TestManagementAPIDelegate> delegate_ = nullptr;
scoped_refptr<const Extension> extension_;
};
#if BUILDFLAG(ENABLE_EXTENSIONS)
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTestWithSetup, SetEnabled_ParentApproves) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
ASSERT_EQ(0, delegate_->enable_count_);
ASSERT_EQ(0, supervised_user_delegate_->show_dialog_count());
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The parent will approve.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kApproved);
// Simulate a call to chrome.management.setEnabled(). It should succeed.
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_->id(),
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success) << error;
EXPECT_TRUE(error.empty());
// Parent permission dialog was opened.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
// Extension was enabled.
EXPECT_EQ(1, delegate_->enable_count_);
}
TEST_F(ManagementApiSupervisedUserTestWithSetup, SetEnabled_ParentDenies) {
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The parent will deny the next dialog.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kCanceled);
// Simulate a call to chrome.management.setEnabled(). It should not succeed.
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_->id(),
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
// Parent permission dialog was opened.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
// Extension was not enabled.
EXPECT_EQ(0, delegate_->enable_count_);
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(ManagementApiSupervisedUserTestWithSetup, SetEnabled_DialogFails) {
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The next dialog will close due to a failure (e.g. network failure while
// looking up parent information).
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kFailed);
// Simulate a call to chrome.management.setEnabled(). It should not succeed.
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_->id(),
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_FALSE(error.empty());
// Extension was not enabled.
EXPECT_EQ(0, delegate_->enable_count_);
}
TEST_F(ManagementApiSupervisedUserTestWithSetup, SetEnabled_PreviouslyAllowed) {
// Disable the extension.
registrar()->DisableExtension(extension_->id(),
{disable_reason::DISABLE_USER_ACTION});
// Simulate previous parent approval.
GetSupervisedUserExtensionsDelegate()->AddExtensionApproval(*extension_);
// Simulate a call to chrome.management.setEnabled().
std::string error;
bool success = RunSetEnabledFunction(web_contents_.get(), extension_->id(),
/*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success) << error;
EXPECT_TRUE(error.empty());
// Parent permission dialog was not opened.
EXPECT_EQ(0, supervised_user_delegate_->show_dialog_count());
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests launching the Parent Permission Dialog from a background page, where
// there isn't active web contents. The parent approves the request.
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTestWithSetup,
SetEnabled_ParentPermissionApprovedFromBackgroundPage) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The parent will approve.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kApproved);
// Simulate a call to chrome.management.setEnabled(). It should succeed
// despite a lack of web contents.
std::string error;
bool success = RunSetEnabledFunction(
/*web_contents=*/nullptr, extension_->id(), /*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_TRUE(success);
EXPECT_TRUE(error.empty());
// Parent Permission Dialog still opened despite the lack of web contents.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
EXPECT_EQ(0, supervised_user_delegate_->show_block_dialog_count());
// Extension is now enabled.
EXPECT_EQ(1, delegate_->enable_count_);
}
// Tests launching the Parent Permission Dialog from a background page, where
// there isn't active web contents. The parent cancels the request.
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTestWithSetup,
SetEnabled_ParentPermissionCanceledFromBackgroundPage) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The parent will cancel.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kCanceled);
// Simulate a call to chrome.management.setEnabled() with no web contents.
std::string error;
bool success = RunSetEnabledFunction(
/*web_contents=*/nullptr, extension_->id(), /*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_EQ(extension_management_api_constants::kUserDidNotReEnableError,
error);
// Parent Permission Dialog still opened despite the lack of web contents.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
EXPECT_EQ(0, supervised_user_delegate_->show_block_dialog_count());
// Extension was not enabled.
EXPECT_EQ(0, delegate_->enable_count_);
}
// Tests launching the Parent Permission Dialog from a background page, where
// there isn't active web contents. The request will fail due to some sort of
// error, such as a network error.
// TODO(crbug.com/402488726): Enable on desktop Android when supervised users
// are fully supported, in particular the install approval dialog.
TEST_F(ManagementApiSupervisedUserTestWithSetup,
SetEnabled_ParentPermissionFailedFromBackgroundPage) {
// Preconditions.
ASSERT_TRUE(profile()->IsChild());
// Start with a disabled extension that needs parent permission.
registrar()->DisableExtension(
extension_->id(), {disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED});
// The request will fail.
supervised_user_delegate_->set_next_parent_permission_dialog_result(
SupervisedUserExtensionsDelegate::ExtensionApprovalResult::kFailed);
// Simulate a call to chrome.management.setEnabled() with no web contents.
std::string error;
bool success = RunSetEnabledFunction(
/*web_contents=*/nullptr, extension_->id(), /*use_user_gesture=*/true,
/*accept_dialog=*/true, &error);
EXPECT_FALSE(success);
EXPECT_EQ(extension_management_api_constants::kParentPermissionFailedError,
error);
// Parent Permission Dialog still opened despite the lack of web contents.
EXPECT_EQ(1, supervised_user_delegate_->show_dialog_count());
EXPECT_EQ(0, supervised_user_delegate_->show_block_dialog_count());
// Extension was not enabled.
EXPECT_EQ(0, delegate_->enable_count_);
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace
} // namespace extensions