blob: 1e8c685f5bc8721c2388eba3381330a7a9b7a8c6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/test/gtest_util.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.h"
#include "chrome/browser/supervised_user/supervised_user_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/supervised_user/supervision_mixin.h"
#include "components/supervised_user/core/browser/supervised_user_preferences.h"
#include "components/supervised_user/core/browser/supervised_user_utils.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "components/supervised_user/core/common/supervised_user_constants.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/supervised_user_extensions_delegate.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/supervised_user/chromeos/parent_access_extension_approvals_manager.h"
#include "chrome/browser/ui/webui/ash/parent_access/fake_parent_access_dialog.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
constexpr char kGoodCrxId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
} // namespace
namespace extensions {
using SupervisionMixinSigninModeCallback =
base::RepeatingCallback<supervised_user::SupervisionMixin::SignInMode()>;
// Tests interaction between supervised users and extensions.
class SupervisionExtensionTestBase
: public InProcessBrowserTestMixinHostSupport<ExtensionBrowserTest>,
public ::testing::WithParamInterface<SupervisionMixinSigninModeCallback> {
public:
SupervisionExtensionTestBase() = default;
protected:
bool IsDisabledForCustodianApproval(const std::string& extension_id) {
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile());
return extension_prefs->HasDisableReason(
extension_id,
extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED);
}
void InstallExtensionAndCheckStatus(
bool should_be_loaded,
bool should_be_enabled,
const std::string& extension_id = kGoodCrxId,
const std::string& extension_crx = "good.crx") {
base::FilePath path = test_data_dir_.AppendASCII(extension_crx);
EXPECT_EQ(LoadExtension(path) != nullptr, should_be_loaded);
const Extension* extension =
extension_registry()->GetInstalledExtension(extension_id);
EXPECT_TRUE(extension);
EXPECT_EQ(
extension_registry()->disabled_extensions().Contains(extension_id),
!should_be_enabled);
EXPECT_EQ(IsDisabledForCustodianApproval(extension_id), !should_be_enabled);
EXPECT_EQ(extension_registry()->enabled_extensions().Contains(extension_id),
should_be_enabled);
}
supervised_user::SupervisionMixin::SignInMode GetMixinSigninMode() {
return GetParam().Run();
}
private:
supervised_user::SupervisionMixin supervision_mixin_{
mixin_host_,
this,
embedded_test_server(),
{.sign_in_mode = GetMixinSigninMode()}};
};
// Tests interaction between supervised users and extensions after the optional
// supervision is removed from the account.
class SupervisionRemovalExtensionTest : public SupervisionExtensionTestBase {};
// Tests that removing supervision should also remove associated disable
// reasons, such as DISABLE_CUSTODIAN_APPROVAL_REQUIRED. Extensions should
// become enabled again after removing supervision. Prevents a regression to
// crbug.com/1045625.
IN_PROC_BROWSER_TEST_P(SupervisionRemovalExtensionTest,
PRE_RemoveCustodianApprovalRequirement) {
ASSERT_TRUE(profile()->IsChild());
base::FilePath path = test_data_dir_.AppendASCII("good.crx");
bool extension_should_be_loaded = false;
EXPECT_EQ(LoadExtension(path) != nullptr, extension_should_be_loaded);
const Extension* extension =
extension_registry()->GetInstalledExtension(kGoodCrxId);
EXPECT_TRUE(extension);
// This extension is a supervised user initiated install and should remain
// disabled.
EXPECT_TRUE(extension_registry()->disabled_extensions().Contains(kGoodCrxId));
EXPECT_TRUE(IsDisabledForCustodianApproval(kGoodCrxId));
}
IN_PROC_BROWSER_TEST_P(SupervisionRemovalExtensionTest,
RemoveCustodianApprovalRequirement) {
ASSERT_FALSE(profile()->IsChild());
// The extension should still be installed since we are sharing the same data
// directory as the PRE test.
const Extension* extension =
extension_registry()->GetInstalledExtension(kGoodCrxId);
EXPECT_TRUE(extension);
// The extension should be enabled now after removing supervision.
EXPECT_TRUE(extension_registry()->enabled_extensions().Contains(kGoodCrxId));
EXPECT_FALSE(
extension_registry()->disabled_extensions().Contains(kGoodCrxId));
EXPECT_FALSE(IsDisabledForCustodianApproval(kGoodCrxId));
}
INSTANTIATE_TEST_SUITE_P(
All,
SupervisionRemovalExtensionTest,
testing::Values(base::BindRepeating([]() {
// The test covers the removal of supervision. Pre-test should start with
// a supervised profile, main test with a regular profile.
return content::IsPreTest()
? supervised_user::SupervisionMixin::SignInMode::kSupervised
: supervised_user::SupervisionMixin::SignInMode::kRegular;
})));
// Tests interaction between supervised users and extensions after the optional
// supervision is added to an the account.
class UserGellerizationExtensionTest : public SupervisionExtensionTestBase {
public:
UserGellerizationExtensionTest() = default;
};
IN_PROC_BROWSER_TEST_P(UserGellerizationExtensionTest,
PRE_UserGellerizationDisablesExistingExtensions) {
ASSERT_FALSE(profile()->IsChild());
base::FilePath path = test_data_dir_.AppendASCII("good.crx");
EXPECT_TRUE(LoadExtension(path) != nullptr);
const Extension* extension =
extension_registry()->GetInstalledExtension(kGoodCrxId);
EXPECT_TRUE(extension);
// The extension is installed and enabled.
EXPECT_TRUE(extension_registry()->enabled_extensions().Contains(kGoodCrxId));
}
IN_PROC_BROWSER_TEST_P(UserGellerizationExtensionTest,
UserGellerizationDisablesExistingExtensions) {
ASSERT_TRUE(profile()->IsChild());
// The extension should still be installed since we are sharing the same data
// directory as the PRE test.
const Extension* extension =
extension_registry()->GetInstalledExtension(kGoodCrxId);
EXPECT_TRUE(extension);
// The extension should be disabled, pending parent approval.
EXPECT_TRUE(extension_registry()->disabled_extensions().Contains(kGoodCrxId));
EXPECT_TRUE(IsDisabledForCustodianApproval(kGoodCrxId));
}
INSTANTIATE_TEST_SUITE_P(
All,
UserGellerizationExtensionTest,
testing::Values(base::BindRepeating([]() {
// Pre-test should start with a regular
// profile, main test with a supervised profile.
return content::IsPreTest()
? supervised_user::SupervisionMixin::SignInMode::kRegular
: supervised_user::SupervisionMixin::SignInMode::kSupervised;
})));
// Tests the parental controls applied on extensions for supervised users
// under different values of the Family Link Extensions Switch
// ("Allow to add extensions without asking for permission").
class ParentApprovalHandlingByExtensionSwitchTest
: public SupervisionExtensionTestBase {
public:
ParentApprovalHandlingByExtensionSwitchTest() = default;
};
IN_PROC_BROWSER_TEST_P(ParentApprovalHandlingByExtensionSwitchTest,
GrantParentApprovalWhenExtensionSwitchBecomesEnabled) {
ASSERT_TRUE(profile()->IsChild());
// Set the Extensions preference to OFF, requiring parent approval for
// installations.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
bool should_be_loaded = false;
bool should_be_enabled = false;
InstallExtensionAndCheckStatus(should_be_loaded, should_be_enabled);
// Flip the Extensions preference to ON.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
// The extension should become enabled and parent-approved.
EXPECT_TRUE(extension_registry()->enabled_extensions().Contains(kGoodCrxId));
// Flip the Extensions preference to OFF.
// The previously approved and enabled extensions should remain enabled.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
EXPECT_TRUE(extension_registry()->enabled_extensions().Contains(kGoodCrxId));
}
IN_PROC_BROWSER_TEST_P(
ParentApprovalHandlingByExtensionSwitchTest,
GrantParentParentApprovalOnInstallationIfExtensionSwitchEnabled) {
// Set the Extensions preference to ON.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
bool should_be_loaded = true;
bool should_be_enabled = should_be_loaded;
InstallExtensionAndCheckStatus(should_be_loaded, should_be_enabled);
// Flip the Extensions preference to OFF.
// The previously approved and enabled extensions should remain enabled.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
EXPECT_EQ(should_be_enabled,
extension_registry()->enabled_extensions().Contains(kGoodCrxId));
}
INSTANTIATE_TEST_SUITE_P(
All,
ParentApprovalHandlingByExtensionSwitchTest,
testing::Values(base::BindRepeating([]() {
return supervised_user::SupervisionMixin::SignInMode::kSupervised;
})));
class ParentApprovalRequestTest
: public SupervisionExtensionTestBase,
public TestParentPermissionDialogViewObserver {
public:
ParentApprovalRequestTest() : TestParentPermissionDialogViewObserver(this) {}
// TestParentPermissionDialogViewObserver implementation:
void OnTestParentPermissionDialogViewCreated(
ParentPermissionDialogView* view) override {
parent_permission_dialog_appeared_ = true;
}
void ClearCustodianPrefs() {
// Clears the preferences relating to both custodians.
profile()->GetPrefs()->ClearPref(prefs::kSupervisedUserCustodianProfileURL);
profile()->GetPrefs()->ClearPref(prefs::kSupervisedUserCustodianName);
profile()->GetPrefs()->ClearPref(prefs::kSupervisedUserCustodianEmail);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserCustodianObfuscatedGaiaId);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserCustodianProfileImageURL);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserSecondCustodianProfileURL);
profile()->GetPrefs()->ClearPref(prefs::kSupervisedUserSecondCustodianName);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserSecondCustodianEmail);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserSecondCustodianObfuscatedGaiaId);
profile()->GetPrefs()->ClearPref(
prefs::kSupervisedUserSecondCustodianProfileImageURL);
}
bool parent_permission_dialog_appeared_ = false;
};
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Tests that installation fails when the custodian info is missing and the
// right histograms are recorded. Regression test for crbug.com/35071637.
IN_PROC_BROWSER_TEST_P(ParentApprovalRequestTest,
RequestToInstallExtensionMissingCustodianInfo) {
// Set the preferences to the default values from Family Link.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("An extension").Build();
CHECK(extension);
ClearCustodianPrefs();
// Request Approval to add a new extension.
base::HistogramTester histogram_tester;
SkBitmap icon;
auto supervised_user_extensions_delegate =
std::make_unique<SupervisedUserExtensionsDelegateImpl>(profile());
supervised_user_extensions_delegate->RequestToAddExtensionOrShowError(
*extension.get(), browser()->tab_strip_model()->GetActiveWebContents(),
gfx::ImageSkia::CreateFrom1xBitmap(icon), base::DoNothing());
// The dialog should not have appeared.
EXPECT_FALSE(parent_permission_dialog_appeared_);
histogram_tester.ExpectBucketCount(SupervisedUserExtensionsMetricsRecorder::
kParentPermissionDialogHistogramName,
SupervisedUserExtensionsMetricsRecorder::
ParentPermissionDialogState::kFailed,
1);
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::
kParentPermissionDialogHistogramName,
SupervisedUserExtensionsMetricsRecorder::ParentPermissionDialogState::
kNoParentError,
1);
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Tests that the method to request extension approval can be triggered
// without errors for new (uninstalled) extensions that already have been
// granted parent approval.
// Prevents regressions to b/321016032.
IN_PROC_BROWSER_TEST_P(ParentApprovalRequestTest,
RequestToInstallApprovedExtension) {
// Create and extension and give it parent approval.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("An extension").Build();
CHECK(extension);
base::Value::Dict approved_extensions;
approved_extensions.Set(extension->id(), true);
profile()->GetPrefs()->SetDict(prefs::kSupervisedUserApprovedExtensions,
std::move(approved_extensions));
ASSERT_FALSE(extension_registry()->GetInstalledExtension(extension->id()));
// Request Approval to add a new extension,
auto supervised_user_extensions_delegate =
std::make_unique<SupervisedUserExtensionsDelegateImpl>(profile());
#if BUILDFLAG(IS_CHROMEOS)
auto fake_parent_access_dialog_provider =
std::make_unique<ash::FakeParentAccessDialogProvider>();
auto fake_parent_access_dialog_provider_ptr =
fake_parent_access_dialog_provider.get();
supervised_user_extensions_delegate
->SetParentAccessExtensionApprovalsManagerForTesting(
std::make_unique<ParentAccessExtensionApprovalsManager>(
std::move(fake_parent_access_dialog_provider)));
fake_parent_access_dialog_provider_ptr->SetNextAction(
ash::FakeParentAccessDialogProvider::Action::CaptureCallback(
base::DoNothing()));
#endif
SkBitmap icon;
supervised_user_extensions_delegate->RequestToAddExtensionOrShowError(
*extension.get(), browser()->tab_strip_model()->GetActiveWebContents(),
gfx::ImageSkia::CreateFrom1xBitmap(icon), base::DoNothing());
// Confirm that the parent approval dialog for extensions for each OS is
// created.
#if BUILDFLAG(IS_CHROMEOS)
EXPECT_TRUE(fake_parent_access_dialog_provider_ptr->TakeLastParams());
#else
EXPECT_TRUE(parent_permission_dialog_appeared_);
#endif
}
INSTANTIATE_TEST_SUITE_P(
All,
ParentApprovalRequestTest,
testing::Values(base::BindRepeating([]() {
return supervised_user::SupervisionMixin::SignInMode::kSupervised;
})));
} // namespace extensions