blob: 87d7503bfea64e9956072889289a9bf0deafe3fe [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 <string>
#include <tuple>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/api/webstore_private/webstore_private_api.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_with_install.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_extensions_metrics_recorder.h"
#include "chrome/browser/supervised_user/supervised_user_test_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/supervised_user/core/common/features.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "extensions/browser/api_test_utils.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_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::api_test_utils::RunFunctionAndReturnSingleResult;
namespace {
const char good_crx[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
const char autoupdate[] = "ogjcoiohnmldgjemafoockdghcjciccf";
const char permissions_increase[] = "pgdpcfcocojkjfbgpiianjngphoopgmo";
} // namespace
namespace extensions {
// Base class for the extension parental controls tests for supervised users.
class SupervisedUserExtensionTestBase : public ExtensionServiceTestWithInstall {
public:
void InitServices(bool profile_is_supervised) {
ExtensionServiceInitParams params;
params.profile_is_supervised = profile_is_supervised;
InitializeExtensionService(std::move(params));
CreateExtensionManager();
}
void CreateExtensionManager() {
supervised_user_extensions_delegate_ =
std::make_unique<SupervisedUserExtensionsDelegateImpl>(profile());
}
ExtensionServiceTestWithInstall::InstallState GetDefaultInstalledState() {
// Default behavior:
// When parental controls are enabled the extensions will be installed
// but disabled until custodian approvals are performed in the offered
// default modes.
// When parental controls are disabled the extensions will be installed
// and enabled.
return ApplyParentalControlsOnExtensions() ? INSTALL_WITHOUT_LOAD
: INSTALL_NEW;
}
const Extension* InstallPermissionsTestExtension(
ExtensionServiceTestWithInstall::InstallState install_state) {
const Extension* extension = InstallTestExtension(
permissions_increase, dir_path("1"), pem_path(), install_state);
return extension;
}
void UpdatePermissionsTestExtension(const std::string& id,
const std::string& version,
UpdateState expected_state) {
UpdateTestExtension(dir_path(version), pem_path(), id, version,
expected_state);
}
const Extension* InstallNoPermissionsTestExtension(
ExtensionServiceTestWithInstall::InstallState install_state) {
base::FilePath base_path = data_dir().AppendASCII("autoupdate");
base::FilePath pem_path = base_path.AppendASCII("key.pem");
base::FilePath dir_path = base_path.AppendASCII("v1");
return InstallTestExtension(autoupdate, dir_path, pem_path, install_state);
}
void UpdateNoPermissionsTestExtension(const std::string& id,
const std::string& version,
UpdateState expected_state) {
base::FilePath base_path = data_dir().AppendASCII("autoupdate");
base::FilePath pem_path = base_path.AppendASCII("key.pem");
base::FilePath dir_path = base_path.AppendASCII("v" + version);
UpdateTestExtension(dir_path, pem_path, id, version, expected_state);
}
void UpdateTestExtension(const base::FilePath& dir_path,
const base::FilePath& pem_path,
const std::string& id,
const std::string& version,
const UpdateState& expected_state) {
PackCRXAndUpdateExtension(id, dir_path, pem_path, expected_state);
const Extension* extension = registry()->GetInstalledExtension(id);
ASSERT_TRUE(extension);
// The version should have been updated.
EXPECT_EQ(base::Version(version), extension->version());
}
const Extension* CheckEnabled(const std::string& extension_id) {
EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_id));
EXPECT_FALSE(IsPendingCustodianApproval(extension_id));
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile());
EXPECT_EQ(disable_reason::DISABLE_NONE,
extension_prefs->GetDisableReasons(extension_id));
return registry()->enabled_extensions().GetByID(extension_id);
}
const Extension* CheckDisabledForCustodianApproval(
const std::string& extension_id) {
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
EXPECT_TRUE(IsPendingCustodianApproval(extension_id));
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile());
EXPECT_TRUE(extension_prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED));
return registry()->disabled_extensions().GetByID(extension_id);
}
const Extension* CheckDisabledForPermissionsIncrease(
const std::string& extension_id) {
EXPECT_TRUE(registry()->disabled_extensions().Contains(extension_id));
if (ApplyParentalControlsOnExtensions()) {
EXPECT_TRUE(IsPendingCustodianApproval(extension_id));
}
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile());
EXPECT_TRUE(extension_prefs->HasDisableReason(
extension_id, disable_reason::DISABLE_PERMISSIONS_INCREASE));
return registry()->disabled_extensions().GetByID(extension_id);
}
SupervisedUserExtensionsDelegate* supervised_user_extensions_delegate() {
return supervised_user_extensions_delegate_.get();
}
const Extension* InstallTestExtension(
const std::string& id,
const base::FilePath& dir_path,
const base::FilePath& pem_path,
ExtensionServiceTestWithInstall::InstallState install_state) {
const Extension* extension =
PackAndInstallCRX(dir_path, pem_path, install_state);
// The extension must now be installed.
EXPECT_TRUE(extension);
EXPECT_EQ(extension->id(), id);
if (ApplyParentalControlsOnExtensions() && install_state != INSTALL_NEW) {
CheckDisabledForCustodianApproval(id);
} else {
CheckEnabled(id);
}
EXPECT_EQ(base::Version("1"), extension->version());
return extension;
}
void SetDefaultParentalControlSettings() {
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), true);
}
// Returns whether parental controls should be enabled for this platform.
// Should always be true for ChromeOS, and depends of enabling certain
// features on Win/Linux/Mac.
virtual bool ApplyParentalControlsOnExtensions() = 0;
private:
// Returns true if the extension has disable reason permissions_increase or
// custodian_approval_required. Tests the Webstore Private Api.
bool IsPendingCustodianApproval(const std::string& extension_id) {
auto function = base::MakeRefCounted<
WebstorePrivateIsPendingCustodianApprovalFunction>();
std::optional<base::Value> result(RunFunctionAndReturnSingleResult(
function.get(), "[\"" + extension_id + "\"]", browser_context()));
return result->GetBool();
}
base::FilePath base_path() const {
return data_dir().AppendASCII("permissions_increase");
}
base::FilePath dir_path(const std::string& version) const {
return base_path().AppendASCII("v" + version);
}
base::FilePath pem_path() const {
return base_path().AppendASCII("permissions.pem");
}
std::unique_ptr<SupervisedUserExtensionsDelegateImpl>
supervised_user_extensions_delegate_;
};
enum class ExtensionsParentalControlState : int { kEnabled = 0, kDisabled = 1 };
enum class ExtensionManagementSwitch : int {
kManagedByExtensions = 0,
kManagedByPermissions = 1
};
class SupervisedUserExtensionTest
: public SupervisedUserExtensionTestBase,
public ::testing::WithParamInterface<
std::tuple<ExtensionsParentalControlState,
ExtensionManagementSwitch>> {
public:
SupervisedUserExtensionTest() {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
// Parental restrictions on the extensions installations for supervised
// users on Desktop apply when the feature
// kEnableExtensionsPermissionsForSupervisedUsersOnDesktop is enabled.
// Extension parental controls for supervised users are already enabled on
// ChromeOS by default.
if (ApplyParentalControlsOnExtensions()) {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
enabled_features.push_back(
supervised_user::
kEnableExtensionsPermissionsForSupervisedUsersOnDesktop);
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
if (GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions) {
// Managed by preference `SkipParentApprovalToInstallExtensions` (new
// flow).
enabled_features.push_back(
supervised_user::
kEnableSupervisedUserSkipParentApprovalToInstallExtensions);
} else {
disabled_features.push_back(
supervised_user::
kEnableSupervisedUserSkipParentApprovalToInstallExtensions);
}
} else {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
disabled_features.push_back(
supervised_user::
kEnableExtensionsPermissionsForSupervisedUsersOnDesktop);
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
bool ApplyParentalControlsOnExtensions() override {
return std::get<0>(GetParam()) == ExtensionsParentalControlState::kEnabled;
}
ExtensionManagementSwitch GetExtensionManagementSwitch() {
return std::get<1>(GetParam());
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that regular users are not affecting supervised user UMA metrics.
TEST_P(SupervisedUserExtensionTest,
RegularUsersNotAffectingSupervisedUserMetrics) {
InitServices(/*profile_is_supervised=*/false);
base::HistogramTester histogram_tester;
base::FilePath path = data_dir().AppendASCII("good.crx");
// The extensions will be installed and enabled.
const Extension* extension = InstallCRX(path, INSTALL_NEW);
ASSERT_TRUE(extension);
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 0);
supervised_user_extensions_delegate()->RemoveExtensionApproval(*extension);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 0);
}
// Tests that simulating custodian approval for regular users doesn't cause any
// unexpected behavior.
TEST_P(SupervisedUserExtensionTest,
CustodianApprovalDoesNotAffectRegularUsers) {
InitServices(/*profile_is_supervised=*/false);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
// Install an extension, it should be enabled because this is a regular user.
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, INSTALL_NEW);
std::string id = extension->id();
CheckEnabled(id);
// Simulate custodian approval and removal.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
supervised_user_extensions_delegate()->RemoveExtensionApproval(*extension);
// The extension should still be enabled.
CheckEnabled(id);
}
// Tests that if the extension parental controls are enabled, adding supervision
// to a regular account (Gellerization) disables previously installed
// extensions. Otherwise the extension remains enabled.
TEST_P(SupervisedUserExtensionTest, ExtensionsStateAfterGellerization) {
InitServices(/*profile_is_supervised=*/false);
SetDefaultParentalControlSettings();
// Install an extension, it should be enabled because this is a regular user.
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, INSTALL_NEW);
ASSERT_TRUE(extension);
std::string id = extension->id();
CheckEnabled(id);
// Now make the profile supervised.
profile()->AsTestingProfile()->SetIsSupervisedProfile();
if (ApplyParentalControlsOnExtensions()) {
// The extension should be disabled now pending custodian approval.
CheckDisabledForCustodianApproval(id);
// Grant parent approval.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
// The extension should be enabled now.
CheckEnabled(id);
// Remove parent approval.
supervised_user_extensions_delegate()->RemoveExtensionApproval(*extension);
// The extension should be disabled again now.
CheckDisabledForCustodianApproval(id);
} else {
// The extension should still be enabled.
CheckEnabled(id);
}
}
// Tests that extensions that are disabled pending parent approval
// for supervised users, become re-enabled if the user becomes unsupervised.
TEST_P(SupervisedUserExtensionTest, ExtensionsStateAfterGraduation) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
// When extension parental controls are enabled on the current platform the
// extensions will be installed but disabled until the custodian approval.
// When extension parental controls are disabled the extensions will be
// installed and enabled.
auto install_state =
ApplyParentalControlsOnExtensions() ? INSTALL_WITHOUT_LOAD : INSTALL_NEW;
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, install_state);
ASSERT_TRUE(extension);
std::string id = extension->id();
if (ApplyParentalControlsOnExtensions()) {
// This extension is a supervised user initiated install and should remain
// disabled.
CheckDisabledForCustodianApproval(id);
} else {
// The new installed extension should be enabled.
CheckEnabled(id);
}
// Make the profile un-supervised.
profile()->AsTestingProfile()->SetIsSupervisedProfile(false);
// The extension should become enabled.
CheckEnabled(id);
}
// Tests that a child user is allowed to install extensions under the default
// values of the Family Link "Permissions" and "Extensions" toggles.
// If the extension parental controls apply the newly-installed extensions
// are disabled until approved by the parent.
// Otherwise the newly-installed extensions are enabled.
TEST_P(SupervisedUserExtensionTest, InstallAllowedForSupervisedUser) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
auto install_state = GetDefaultInstalledState();
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, install_state);
ASSERT_TRUE(extension);
std::string id = extension->id();
if (ApplyParentalControlsOnExtensions()) {
// This extension is a supervised user initiated install and should remain
// disabled.
CheckDisabledForCustodianApproval(id);
// Grant parent approval.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
// The extension is now enabled.
CheckEnabled(id);
// Remove parent approval.
supervised_user_extensions_delegate()->RemoveExtensionApproval(*extension);
// The extension should be disabled again now.
CheckDisabledForCustodianApproval(id);
} else {
// The new installed extension should be enabled.
CheckEnabled(id);
}
}
// Tests that supervised users may approve permission updates without parent
// approval under the default values of the Family Link "Permissions" and
// "Extensions" toggles, when parental controls apply to extensions.
// If parental controls do not apply, the child can approve permission
// updates by default.
TEST_P(SupervisedUserExtensionTest, UpdateWithPermissionsIncrease) {
InitServices(true);
SetDefaultParentalControlSettings();
// Preconditions.
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 0);
base::UserActionTester user_action_tester;
EXPECT_EQ(
0,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kApprovalGrantedActionName));
EXPECT_EQ(
0,
user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::kApprovalRemovedActionName));
std::string id =
InstallPermissionsTestExtension(GetDefaultInstalledState())->id();
if (ApplyParentalControlsOnExtensions()) {
// Simulate parent approval.
supervised_user_extensions_delegate()->AddExtensionApproval(
*registry()->GetInstalledExtension(id));
// Should see 1 kApprovalGranted metric count.
histogram_tester.ExpectUniqueSample(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 1);
EXPECT_EQ(1, user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::
kApprovalGrantedActionName));
}
// The extension should be enabled.
CheckEnabled(id);
// Update to a new version with increased permissions.
UpdatePermissionsTestExtension(id, "2", DISABLED);
const Extension* extension = CheckDisabledForPermissionsIncrease(id);
ASSERT_TRUE(extension);
// Simulate supervised user approving the extension without further parent
// approval.
service()->GrantPermissionsAndEnableExtension(extension);
// The extension should be enabled.
CheckEnabled(id);
if (ApplyParentalControlsOnExtensions()) {
// Remove extension approval.
supervised_user_extensions_delegate()->RemoveExtensionApproval(
*registry()->GetInstalledExtension(id));
// Should see 1 kApprovalRemoved metric count.
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalRemoved,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 2);
EXPECT_EQ(1, user_action_tester.GetActionCount(
SupervisedUserExtensionsMetricsRecorder::
kApprovalRemovedActionName));
// The extension should be disabled now.
CheckDisabledForCustodianApproval(id);
}
}
// Tests that when extensions are managed by the "Permissions" Family Link
// switch, if the toggle is disabled, then:
// If the extension parental controls are enabled, child users cannot approve
// permission updates, otherwise they can approve permission updates.
// When extensions are managed by the "Extensions" Family Link switch,
// toggling the "Permissions" switch has no effect.
TEST_P(SupervisedUserExtensionTest,
ChildUserCannotApproveAdditionalPermissions) {
InitServices(/*profile_is_supervised=*/true);
// Default settings allow to install the extension.
SetDefaultParentalControlSettings();
base::HistogramTester histogram_tester;
std::string id =
InstallPermissionsTestExtension(GetDefaultInstalledState())->id();
const Extension* extension1 = nullptr;
if (ApplyParentalControlsOnExtensions()) {
extension1 = CheckDisabledForCustodianApproval(id);
ASSERT_TRUE(extension1);
// Simulate parent granting approval for the initial version.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension1);
// The extension should be enabled now.
CheckEnabled(id);
// Should see 1 kApprovalGranted metric count.
histogram_tester.ExpectUniqueSample(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 1);
} else {
// The extension is installed as enabled.
extension1 = CheckEnabled(id);
ASSERT_TRUE(extension1);
}
// Update to a new version with increased permissions.
std::string version2("2");
UpdatePermissionsTestExtension(id, version2, DISABLED);
const Extension* extension2 = CheckDisabledForPermissionsIncrease(id);
ASSERT_TRUE(extension2);
// Flip the Permissions toggle to off.
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
// Now the extension is blocked since it requires additional permissions.
// Simulate child granting approval for the new permissions.
service()->GrantPermissionsAndEnableExtension(extension2);
if (ApplyParentalControlsOnExtensions() &&
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByPermissions) {
// If the extensions are managed by the Permissions Family Link switch, then
// the extension is still disabled because the child cannot grant additional
// permissions.
CheckDisabledForPermissionsIncrease(id);
} else {
// The extension should now be enabled and the version number increased.
extension2 = CheckEnabled(id);
EXPECT_TRUE(extension2);
EXPECT_EQ(extension2->version(), base::Version(version2));
}
}
// Tests that if an approved extension is updated to a newer version that
// doesn't require additional permissions, it is still enabled.
TEST_P(SupervisedUserExtensionTest, UpdateWithoutPermissionIncrease) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
const Extension* extension =
InstallNoPermissionsTestExtension(GetDefaultInstalledState());
// Save the id, as the extension object will be destroyed during updating.
std::string id = extension->id();
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
// The extension should be enabled now.
CheckEnabled(id);
// Update to a new version.
std::string version2("2");
UpdateNoPermissionsTestExtension(id, version2, ENABLED);
// The extension should still be there and enabled.
const Extension* extension1 = CheckEnabled(id);
ASSERT_TRUE(extension1);
// The version should have changed.
EXPECT_EQ(base::Version(version2), extension1->version());
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
std::string version3("3");
UpdateNoPermissionsTestExtension(id, version3, ENABLED);
// The extension should still be there and enabled.
const Extension* extension2 = CheckEnabled(id);
ASSERT_TRUE(extension2);
// The version should have changed again.
EXPECT_EQ(base::Version(version3), extension2->version());
if (ApplyParentalControlsOnExtensions()) {
// Check that the approved extension id has been updated in the prefs as
// well. Prefs are updated via sync.
PrefService* pref_service = profile()->GetPrefs();
ASSERT_TRUE(pref_service);
const base::Value::Dict& approved_extensions =
pref_service->GetDict(prefs::kSupervisedUserApprovedExtensions);
EXPECT_TRUE(approved_extensions.FindString(id));
}
}
// Tests that when extensions are managed by the "Permissions" Family Link
// toggle, if the "Permissions" toggle is disabled, then:
// - If extension parental controls are enabled child users cannot install new
// extensions.
// - If extension parental controls are disabled child users can install new
// extensions.
// When extensions are managed by the "Extensions" Family Link toggle,
// toggling the "Permissions" switch has no effect to the installation.
TEST_P(SupervisedUserExtensionTest,
SupervisedUserCannotInstallExtensionUnderPermissionsToggle) {
InitServices(/*profile_is_supervised=*/true);
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
base::FilePath path = data_dir().AppendASCII("good.crx");
// When extension parental controls are enabled the extensions will fail
// installation. When extension parental control are disabled the extensions
// will be installed and enabled.
if (ApplyParentalControlsOnExtensions()) {
if (GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByPermissions) {
// Installation has failed.
const Extension* extension = InstallCRX(path, INSTALL_FAILED);
EXPECT_FALSE(extension);
} else if (GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions) {
// Installation is successful. The extension is installed disabled in the
// default mode.
const Extension* extension = InstallCRX(path, INSTALL_WITHOUT_LOAD);
EXPECT_TRUE(extension);
CheckDisabledForCustodianApproval(extension->id());
}
} else {
const Extension* extension = InstallCRX(path, INSTALL_NEW);
EXPECT_TRUE(extension);
CheckEnabled(extension->id());
}
}
// Tests that disabling the "Permissions" Family Link toggle,
// has no effect on regular users.
TEST_P(SupervisedUserExtensionTest, RegularUserCanInstallExtension) {
InitServices(/*profile_is_supervised=*/false);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
base::FilePath path = data_dir().AppendASCII("good.crx");
// The extension should be installed and enabled.
const Extension* extension = InstallCRX(path, INSTALL_NEW);
ASSERT_TRUE(extension);
CheckEnabled(extension->id());
}
// Tests that if the "Permissions" Family Link toggle becomes disabled,
// previously approved extensions are still enabled.
TEST_P(SupervisedUserExtensionTest,
PermissionsToggleOffDoesNotAffectAlreadyEnabled) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
// The installation helper function checks that the extension is initially
// disabled.
const Extension* extension =
InstallNoPermissionsTestExtension(GetDefaultInstalledState());
std::string id = extension->id();
if (ApplyParentalControlsOnExtensions()) {
// Now approve the extension.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
}
// The extension should be enabled now.
CheckEnabled(id);
// Custodian toggles "Permissions for sites, apps and extensions" to false.
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), false);
// Already installed and enabled extensions should remain that way.
CheckEnabled(id);
}
// Tests that extensions installed when the "Extensions" Family Link toggle
// applies and is enabled, are installed enabled and have been granted parent
// approval.
TEST_P(SupervisedUserExtensionTest,
ExtensionsToggleOnGrantsParentApprovalOnInstallation) {
InitServices(/*profile_is_supervised=*/true);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), true);
// Set the "Extensions" toggle to true, allowing installation without parental
// approval.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
bool should_be_enabled = !ApplyParentalControlsOnExtensions() ||
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions;
// If the Extensions toggle applies, the extension is installed and enabled.
auto install_state =
should_be_enabled ? INSTALL_NEW : GetDefaultInstalledState();
const Extension* extension = InstallNoPermissionsTestExtension(install_state);
std::string id = extension->id();
if (should_be_enabled) {
// The extension has already been granted approval on its installation.
CheckEnabled(id);
} else {
CheckDisabledForCustodianApproval(id);
}
}
// Tests that for extensions installed under the enabled "Extensions" Family
// Link toggle the approval remains on installed extensions if the switch is
// toggled to false.
TEST_P(SupervisedUserExtensionTest,
ExtensionsToggleOffDoesNotAffectAlreadyEnabled) {
InitServices(/*profile_is_supervised=*/true);
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile(), true);
// Set the "Extensions" toggle to true, allowing installation without parental
// approval.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
bool should_be_enabled = !ApplyParentalControlsOnExtensions() ||
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions;
// If the Extensions toggle applies, the extension is installed and enabled.
auto install_state =
should_be_enabled ? INSTALL_NEW : GetDefaultInstalledState();
const Extension* extension = InstallNoPermissionsTestExtension(install_state);
std::string id = extension->id();
if (should_be_enabled) {
// The extension has already been granted approval on its installation.
CheckEnabled(id);
} else {
CheckDisabledForCustodianApproval(id);
}
// Custodian sets the "Extensions" toggle to false.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
// Already installed and enabled extensions should remain that way.
if (should_be_enabled) {
CheckEnabled(id);
}
}
// Tests that for extensions installed under the enabled "Extensions" Family
// Link, toggling the switch from false to true grants parental approval.
TEST_P(SupervisedUserExtensionTest,
ExtensionsToggleOnGrantsMissingParentalApproval) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
const Extension* extension =
InstallNoPermissionsTestExtension(GetDefaultInstalledState());
std::string id = extension->id();
if (ApplyParentalControlsOnExtensions()) {
CheckDisabledForCustodianApproval(id);
} else {
CheckEnabled(id);
}
// Custodian sets the "Extensions" toggle to True.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
if (!ApplyParentalControlsOnExtensions() ||
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions) {
// If the "Extensions" toggle manages the extensions, the extension has been
// granted approval and becomes enabled on toggling the switch.
CheckEnabled(id);
} else {
// If the "Permissions" toggle manages the extensions, toggling the
// "Extensions" switch has no effect.
CheckDisabledForCustodianApproval(id);
}
}
// Tests the case when the extension approval arrives through sync before the
// extension itself is installed.
TEST_P(SupervisedUserExtensionTest, ExtensionApprovalBeforeInstallation) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
scoped_refptr<const Extension> extension =
ExtensionBuilder(good_crx).SetID(good_crx).SetVersion("0").Build();
if (ApplyParentalControlsOnExtensions()) {
// Now approve the extension.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
}
// Now install an extension, it should be enabled upon installation.
base::FilePath path = data_dir().AppendASCII("good.crx");
InstallCRX(path, INSTALL_NEW);
// Make sure it's enabled.
CheckEnabled(good_crx);
}
// Tests that when the `SkipParentApprovalToInstallExtensions` feature is first
// released (so Extensions are managed by Family Link "Extensions" toggle),
// existing extensions remain enabled on Desktop. On ChromeOS they are disabled.
TEST_P(SupervisedUserExtensionTest,
ExtensionsOnDesktopRemainEnabledOnSkipParentApprovalRelease) {
ExtensionServiceInitParams params;
params.profile_is_supervised = true;
InitializeExtensionService(std::move(params));
SetDefaultParentalControlSettings();
// Install an extension. It should be enabled as we haven't created the SU
// extension manager yet. Treated as a pre-existing extension.
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, INSTALL_NEW);
ASSERT_TRUE(extension);
// Make sure it's enabled.
CheckEnabled(good_crx);
// Create the extensions manager. If the
// `SkipParentApprovalToInstallExtensions` feature applies for the first time,
// the existing extensions remain enabled on Desktop.
CreateExtensionManager();
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
bool should_be_enabled = !ApplyParentalControlsOnExtensions() ||
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions;
#else
bool should_be_enabled = !ApplyParentalControlsOnExtensions();
#endif
if (should_be_enabled) {
CheckEnabled(good_crx);
} else {
CheckDisabledForCustodianApproval(good_crx);
}
if (ApplyParentalControlsOnExtensions()) {
// Parent approval can be granted even if the extension behaves already as
// parent-approved on Win/Linux/Mac.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
}
CheckEnabled(good_crx);
}
// Tests when the `SkipParentApprovalToInstallExtensions` feature is firstly
// released (so Extensions are managed by Family Link "Extensions" toggle)
// existing extensions that have been marked parent-approved on Desktop by
// default can be upgraded without further parental approval.
TEST_P(SupervisedUserExtensionTest,
ExtensionsEnabledOnSkipParentApprovalReleaseCanBeUpgraded) {
ExtensionServiceInitParams params;
params.profile_is_supervised = true;
InitializeExtensionService(std::move(params));
SetDefaultParentalControlSettings();
// Install an extension. It should be enabled as we haven't created the SU
// extension manager yet. Treated as a pre-existing extension.
const Extension* extension = InstallPermissionsTestExtension(INSTALL_NEW);
ASSERT_TRUE(extension);
std::string extension_id = extension->id();
// Make sure it's enabled.
CheckEnabled(extension_id);
// Create the extensions manager. If the
// `SkipParentApprovalToInstallExtensions` feature applies for the first time,
// the existing extensions remain enabled on Desktop.
CreateExtensionManager();
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
bool should_be_enabled = !ApplyParentalControlsOnExtensions() ||
GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions;
#else
bool should_be_enabled = !ApplyParentalControlsOnExtensions();
#endif
if (should_be_enabled) {
CheckEnabled(extension_id);
} else {
CheckDisabledForCustodianApproval(extension_id);
}
// Update to a new version with increased permissions.
UpdatePermissionsTestExtension(extension_id, "2", DISABLED);
const Extension* extension2 =
CheckDisabledForPermissionsIncrease(extension_id);
ASSERT_TRUE(extension2);
// Grant the upgraded permissions.
service()->GrantPermissionsAndEnableExtension(extension2);
if (should_be_enabled) {
// When no parental controls apply, or when Managed by the Extensions
// switch, the extensions becomes enabled upon granting the increased
// permission. The parental approval granted at SU Extension manager
// creation remains.
CheckEnabled(extension_id);
} else {
// When managed by the Permissions switch the extension is still disabled
// as parent approval was never granted.
CheckDisabledForCustodianApproval(extension_id);
}
}
// Tests that uninstalling a parent-approved extension removes the parental
// approval.
TEST_P(SupervisedUserExtensionTest, UnistallingRevokesParentApproval) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
const Extension* extension =
InstallNoPermissionsTestExtension(GetDefaultInstalledState());
std::string extension_id = extension->id();
if (ApplyParentalControlsOnExtensions()) {
CheckDisabledForCustodianApproval(extension_id);
// Simulate parent approval.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
}
CheckEnabled(extension_id);
EXPECT_EQ(ApplyParentalControlsOnExtensions(),
profile()
->GetPrefs()
->GetDict(prefs::kSupervisedUserApprovedExtensions)
.contains(extension_id));
// Uninstall the extension.
std::u16string error;
service()->UninstallExtension(
extension_id, UninstallReason::UNINSTALL_REASON_FOR_TESTING, &error);
EXPECT_FALSE(profile()
->GetPrefs()
->GetDict(prefs::kSupervisedUserApprovedExtensions)
.contains(extension_id));
}
INSTANTIATE_TEST_SUITE_P(
All,
SupervisedUserExtensionTest,
testing::Combine(
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
testing::Values(ExtensionsParentalControlState::kDisabled,
ExtensionsParentalControlState::kEnabled),
#else
// On ChromeOS the extension parental controls are on by default.
testing::Values(ExtensionsParentalControlState::kEnabled),
#endif
testing::Values(ExtensionManagementSwitch::kManagedByExtensions,
ExtensionManagementSwitch::kManagedByPermissions)),
[](const auto& info) {
return std::string(std::get<0>(info.param) ==
ExtensionsParentalControlState::kEnabled
? "WithParentalControlsOnExtensions"
: "WithoutParentalControlsOnExtensions") +
std::string(std::get<1>(info.param) ==
ExtensionManagementSwitch::kManagedByExtensions
? "ManagedByExtensionsSwitch"
: "ManagedByPermissionsSwitch");
});
// Test class for cases that apply only when extension parental controls are
// enabled.
class SupervisedUserWithEnabledExtensionParentalControlsTest
: public SupervisedUserExtensionTestBase,
public ::testing::WithParamInterface<ExtensionManagementSwitch> {
public:
SupervisedUserWithEnabledExtensionParentalControlsTest() {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
// Parental controls on the extensions for supervised users
// on Desktop apply when the feature
// kEnableExtensionsPermissionsForSupervisedUsersOnDesktop is enabled.
// Extension parental controls for supervised users are already enabled on
// ChromeOS by default.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
enabled_features.push_back(
supervised_user::
kEnableExtensionsPermissionsForSupervisedUsersOnDesktop);
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
if (GetExtensionManagementSwitch() ==
ExtensionManagementSwitch::kManagedByExtensions) {
// Managed by preference `SkipParentApprovalToInstallExtensions` (new
// flow).
enabled_features.push_back(
supervised_user::
kEnableSupervisedUserSkipParentApprovalToInstallExtensions);
} else {
disabled_features.push_back(
supervised_user::
kEnableSupervisedUserSkipParentApprovalToInstallExtensions);
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
// SupervisedUserExtensionTestBase implementation:
bool ApplyParentalControlsOnExtensions() override { return true; }
ExtensionManagementSwitch GetExtensionManagementSwitch() {
return (GetParam());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that the kApprovalGranted UMA metric only increments once without
// duplication for the same extension id.
TEST_P(SupervisedUserWithEnabledExtensionParentalControlsTest,
DontTriggerMetricsIfAlreadyApproved) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
base::HistogramTester histogram_tester;
base::FilePath path = data_dir().AppendASCII("good.crx");
// The extension should be installed but disabled until we perform custodian
// approval.
const Extension* extension = InstallCRX(path, INSTALL_WITHOUT_LOAD);
ASSERT_TRUE(extension);
CheckDisabledForCustodianApproval(extension->id());
// Simulate parent approval for the extension installation.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
// The extension should be enabled now.
CheckEnabled(extension->id());
// Should see 1 kApprovalGranted metric count recorded.
histogram_tester.ExpectUniqueSample(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 1);
// Simulate the supervised user disabling and re-enabling the extension
// without changing anything else.
supervised_user_extensions_delegate()->AddExtensionApproval(*extension);
// Should not see another kApprovalGranted metric count recorded because it
// was already approved. The previous step should be a no-op.
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalGranted,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 1);
// Now remove approval.
supervised_user_extensions_delegate()->RemoveExtensionApproval(*extension);
// There should be a kApprovalRemoved metric count.
histogram_tester.ExpectBucketCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName,
SupervisedUserExtensionsMetricsRecorder::UmaExtensionState::
kApprovalRemoved,
1);
histogram_tester.ExpectTotalCount(
SupervisedUserExtensionsMetricsRecorder::kExtensionsHistogramName, 2);
}
// Tests that parent approval is necessary but not sufficient to enable
// extensions when both disable reasons custodian_approval_required and
// permissions_increase are present.
TEST_P(SupervisedUserWithEnabledExtensionParentalControlsTest,
ParentApprovalNecessaryButNotSufficient) {
InitServices(/*profile_is_supervised=*/true);
SetDefaultParentalControlSettings();
std::string id =
InstallPermissionsTestExtension(GetDefaultInstalledState())->id();
// Update to a new version with increased permissions.
UpdatePermissionsTestExtension(id, "2", DISABLED);
// Expect both disable reasons.
CheckDisabledForCustodianApproval(id);
const Extension* extension = CheckDisabledForPermissionsIncrease(id);
ASSERT_TRUE(extension);
// Try to enable the extension without parent approval to prove that it's
// necessary.
service()->GrantPermissionsAndEnableExtension(extension);
// The extension is still disabled.
CheckDisabledForCustodianApproval(id);
CheckDisabledForPermissionsIncrease(id);
// Simulate parent approval.
supervised_user_extensions_delegate()->AddExtensionApproval(
*registry()->GetInstalledExtension(id));
// The extension is still disabled (not sufficient).
CheckDisabledForPermissionsIncrease(id);
// Now grant permissions and try to enable again.
service()->GrantPermissionsAndEnableExtension(extension);
// The extension should be enabled.
CheckEnabled(id);
}
INSTANTIATE_TEST_SUITE_P(
All,
SupervisedUserWithEnabledExtensionParentalControlsTest,
testing::Values(ExtensionManagementSwitch::kManagedByExtensions,
ExtensionManagementSwitch::kManagedByPermissions),
[](const auto& info) {
return std::string(info.param ==
ExtensionManagementSwitch::kManagedByExtensions
? "ManagedByExtensionsSwitch"
: "ManagedByPermissionsSwitch");
});
} // namespace extensions