blob: 54f9c9b73c4cec0df822acd30e36ce1750bd82df [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/supervised_user/supervised_user_extensions_manager.h"
#include <string>
#include <vector>
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/supervised_user_test_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/supervised_user/core/browser/supervised_user_utils.h"
#include "components/supervised_user/core/common/features.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "components/version_info/version_info.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
enum class ExtensionsManagingToggle : int {
/* Extensions are managed by the
"Permissions for sites, apps and extensions" FL button. */
kPermissions = 0,
/* Extensions are managed by the dedicated
"Skip parent approval to install extensions" FL button. */
kExtensions = 1
};
using extensions::Extension;
class SupervisedUserExtensionsManagerTest
: public extensions::ExtensionServiceTestBase,
public ::testing::WithParamInterface<ExtensionsManagingToggle> {
public:
SupervisedUserExtensionsManagerTest() : channel_(version_info::Channel::DEV) {
std::vector<base::test::FeatureRef> enabled_features;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
enabled_features.push_back(
supervised_user::
kEnableExtensionsPermissionsForSupervisedUsersOnDesktop);
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
if (GetParam() == ExtensionsManagingToggle::kExtensions) {
enabled_features.push_back(
supervised_user::
kEnableSupervisedUserSkipParentApprovalToInstallExtensions);
}
scoped_feature_list_.InitWithFeatures(enabled_features,
/*disabled_feature=s*/ {});
}
~SupervisedUserExtensionsManagerTest() override = default;
void SetUp() override {
ExtensionServiceTestBase::SetUp();
ExtensionServiceInitParams params;
params.profile_is_supervised = true;
InitializeExtensionService(params);
// Flush the message loop, to ensure that credentials have been loaded in
// Identity Manager.
base::RunLoop().RunUntilIdle();
}
void TearDown() override {
// Flush the message loop, to ensure all posted tasks run.
base::RunLoop().RunUntilIdle();
}
protected:
scoped_refptr<const extensions::Extension> MakeThemeExtension() {
base::Value::Dict source;
source.Set(extensions::manifest_keys::kName, "Theme");
source.Set(extensions::manifest_keys::kTheme, base::Value::Dict());
source.Set(extensions::manifest_keys::kVersion, "1.0");
extensions::ExtensionBuilder builder;
scoped_refptr<const extensions::Extension> extension =
builder.SetManifest(std::move(source)).Build();
return extension;
}
scoped_refptr<const extensions::Extension> MakeExtension(
std::string name = "Extension") {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(name).Build();
return extension;
}
void MakeSupervisedUserExtensionsManager() {
manager_ = std::make_unique<extensions::SupervisedUserExtensionsManager>(
profile());
}
extensions::ScopedCurrentChannel channel_;
std::unique_ptr<extensions::SupervisedUserExtensionsManager> manager_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(SupervisedUserExtensionsManagerTest,
ExtensionManagementPolicyProviderWithoutSUInitiatedInstalls) {
MakeSupervisedUserExtensionsManager();
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile_.get(),
false);
ASSERT_TRUE(profile_->IsChild());
// Check that a supervised user can install and uninstall a theme even if
// they are not allowed to install extensions.
{
scoped_refptr<const extensions::Extension> theme = MakeThemeExtension();
std::u16string error_1;
EXPECT_TRUE(manager_->UserMayLoad(theme.get(), &error_1));
EXPECT_TRUE(error_1.empty());
std::u16string error_2;
EXPECT_FALSE(manager_->MustRemainInstalled(theme.get(), &error_2));
EXPECT_TRUE(error_2.empty());
}
scoped_refptr<const extensions::Extension> extension = MakeExtension();
if (GetParam() == ExtensionsManagingToggle::kPermissions) {
// Now check a different kind of extension; the supervised user should not
// be able to load it. It should also not need to remain installed.
std::u16string error_1;
EXPECT_FALSE(manager_->UserMayLoad(extension.get(), &error_1));
EXPECT_FALSE(error_1.empty());
std::u16string error_2;
EXPECT_FALSE(manager_->UserMayInstall(extension.get(), &error_2));
EXPECT_FALSE(error_2.empty());
} else {
// Under the "Extensions" switch, installation are always allowed.
std::u16string error_1;
EXPECT_TRUE(manager_->UserMayLoad(extension.get(), &error_1));
EXPECT_TRUE(error_1.empty());
std::u16string error_2;
EXPECT_TRUE(manager_->UserMayInstall(extension.get(), &error_2));
EXPECT_TRUE(error_2.empty());
}
std::u16string error_3;
EXPECT_FALSE(manager_->MustRemainInstalled(extension.get(), &error_3));
EXPECT_TRUE(error_3.empty());
#if DCHECK_IS_ON()
EXPECT_FALSE(manager_->GetDebugPolicyProviderName().empty());
#endif
}
TEST_P(SupervisedUserExtensionsManagerTest,
ExtensionManagementPolicyProviderWithSUInitiatedInstalls) {
MakeSupervisedUserExtensionsManager();
if (GetParam() == ExtensionsManagingToggle::kExtensions) {
// Enable child users to initiate extension installs by simulating the
// toggling of "Skip parent approval to install extensions" to disabled.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
} else {
// Enable child users to initiate extension installs by simulating the
// toggling of "Permissions for sites, apps and extensions" to enabled.
supervised_user_test_util::
SetSupervisedUserExtensionsMayRequestPermissionsPref(profile_.get(),
true);
}
ASSERT_TRUE(profile_->IsChild());
// The supervised user should be able to load and uninstall the extensions
// they install.
{
scoped_refptr<const extensions::Extension> extension = MakeExtension();
std::u16string error;
EXPECT_TRUE(manager_->UserMayLoad(extension.get(), &error));
EXPECT_TRUE(error.empty());
std::u16string error_2;
EXPECT_FALSE(manager_->MustRemainInstalled(extension.get(), &error_2));
EXPECT_TRUE(error_2.empty());
std::u16string error_3;
extensions::disable_reason::DisableReason reason =
extensions::disable_reason::DISABLE_NONE;
EXPECT_TRUE(
manager_->MustRemainDisabled(extension.get(), &reason, &error_3));
EXPECT_EQ(extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED,
reason);
EXPECT_FALSE(error_3.empty());
std::u16string error_4;
EXPECT_TRUE(manager_->UserMayModifySettings(extension.get(), &error_4));
EXPECT_TRUE(error_4.empty());
std::u16string error_5;
EXPECT_TRUE(manager_->UserMayInstall(extension.get(), &error_5));
EXPECT_TRUE(error_5.empty());
}
#if DCHECK_IS_ON()
EXPECT_FALSE(manager_->GetDebugPolicyProviderName().empty());
#endif
}
// Tests that on Desktop (Win/Linux/Mac) platforms, when the feature
// `kEnableSupervisedUserSkipParentApprovalToInstallExtensions` is first
// enabled, present extensions will be marked as locally parent-approved
// when the SupervisedUserExtensionsManager is created for a supervised user.
TEST_P(SupervisedUserExtensionsManagerTest,
MigrateExtensionsToLocallyApproved) {
ASSERT_TRUE(profile_->IsChild());
// Register the extensions.
scoped_refptr<const Extension> approved_extn =
MakeExtension("extension_test_1");
scoped_refptr<const Extension> locally_approved_extn =
MakeExtension("local_extension_test_1");
service()->AddExtension(approved_extn.get());
service()->AddExtension(locally_approved_extn.get());
// Mark one extension as already parent-approved in the corresponding
// preference.
auto* prefs = profile()->GetPrefs();
CHECK(prefs);
base::Value::Dict approved_extensions;
approved_extensions.Set(approved_extn->id(), true);
prefs->SetDict(prefs::kSupervisedUserApprovedExtensions,
std::move(approved_extensions));
// Create the object under test.
MakeSupervisedUserExtensionsManager();
bool has_local_approval_migration_run = false;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
auto expected_migration_state =
GetParam() == ExtensionsManagingToggle::kExtensions
? supervised_user::LocallyParentApprovedExtensionsMigrationState::
kComplete
: supervised_user::LocallyParentApprovedExtensionsMigrationState::
kNeedToRun;
has_local_approval_migration_run =
GetParam() == ExtensionsManagingToggle::kExtensions;
EXPECT_EQ(
static_cast<int>(expected_migration_state),
prefs->GetInteger(prefs::kLocallyParentApprovedExtensionsMigrationState));
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// The already approved extension should be allowed and not part of the
// local-approved list.
const base::Value::Dict& local_approved_extensions_pref =
prefs->GetDict(prefs::kSupervisedUserLocallyParentApprovedExtensions);
EXPECT_FALSE(local_approved_extensions_pref.contains(approved_extn->id()));
EXPECT_TRUE(manager_->IsExtensionAllowed(*approved_extn));
// The extensions approved in the migration should be allowed and part
// of the local-approved list.
EXPECT_EQ(
has_local_approval_migration_run,
local_approved_extensions_pref.contains(locally_approved_extn->id()));
EXPECT_EQ(has_local_approval_migration_run,
manager_->IsExtensionAllowed(*locally_approved_extn));
}
// Tests that extensions missing parent approval are granted parent approval
// on their installation, when the extensions are managed by the Extensions
// toggle and the toggle is ON. If extensions are managed by the Permissions
// toggle, the extensions remain disabled and pending approval.
TEST_P(SupervisedUserExtensionsManagerTest,
GrantParentApprovalOnInstallationWhenExtensionsToggleOn) {
ASSERT_TRUE(profile_->IsChild());
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Mark the migration done to avoid any interference with the one-off
// migration.
auto* prefs = profile()->GetPrefs();
CHECK(prefs);
prefs->SetInteger(
prefs::kLocallyParentApprovedExtensionsMigrationState,
static_cast<int>(
supervised_user::LocallyParentApprovedExtensionsMigrationState::
kComplete));
#endif
// Create the object under test.
MakeSupervisedUserExtensionsManager();
// Set the Extensions switch to OFF. The extensions installed on this mode,
// should be pending approval and disabled.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
// Install an extension.
scoped_refptr<const Extension> extn_with_switch_off =
MakeExtension("extension_test_1");
service()->OnExtensionInstalled(extn_with_switch_off.get(),
/*page_ordinal=*/syncer::StringOrdinal());
extensions::disable_reason::DisableReason reason;
std::u16string error;
EXPECT_FALSE(manager_->IsExtensionAllowed(*extn_with_switch_off.get()));
EXPECT_TRUE(manager_->MustRemainDisabled(extn_with_switch_off.get(), &reason,
&error));
// Set the Extensions switch to ON. Install another extension which should be
// granted parental approval by the end of the installation, if the Extensions
// switch manages them.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
// Install an extension.
scoped_refptr<const Extension> extn_with_switch_on =
MakeExtension("extension_test_2");
service()->OnExtensionInstalled(extn_with_switch_on.get(),
/*page_ordinal=*/syncer::StringOrdinal());
bool is_extension_approved =
GetParam() == ExtensionsManagingToggle::kExtensions ? true : false;
EXPECT_EQ(is_extension_approved,
manager_->IsExtensionAllowed(*extn_with_switch_on.get()));
EXPECT_EQ(is_extension_approved,
!manager_->MustRemainDisabled(extn_with_switch_on.get(), &reason,
&error));
EXPECT_EQ(is_extension_approved,
profile()
->GetPrefs()
->GetDict(prefs::kSupervisedUserApprovedExtensions)
.contains(extn_with_switch_on->id()));
}
// Tests that extensions missing parent approval are granted parent approval
// when the extensions are managed by the Extensions toggle and the toggle is
// flipped to ON.
// If extensions are managed by the Permissions toggle, the extensions remain
// disabled and pending approval.
TEST_P(SupervisedUserExtensionsManagerTest,
GrantParentApprovalOnExtensionsWhenExtensionsToggleSetToOn) {
ASSERT_TRUE(profile_->IsChild());
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Mark the migration done to avoid any interference with the one-off
// migration.
auto* prefs = profile()->GetPrefs();
CHECK(prefs);
prefs->SetInteger(
prefs::kLocallyParentApprovedExtensionsMigrationState,
static_cast<int>(
supervised_user::LocallyParentApprovedExtensionsMigrationState::
kComplete));
#endif
// Create the object under test.
MakeSupervisedUserExtensionsManager();
// Set the Extensions switch to OFF. The extensions installed on this mode,
// should be pending approval and disabled.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), false);
// Install an extension.
scoped_refptr<const Extension> extn_with_switch_off =
MakeExtension("extension_test_1");
service()->OnExtensionInstalled(extn_with_switch_off.get(),
/*page_ordinal=*/syncer::StringOrdinal());
extensions::disable_reason::DisableReason reason;
std::u16string error;
EXPECT_FALSE(manager_->IsExtensionAllowed(*extn_with_switch_off.get()));
EXPECT_TRUE(manager_->MustRemainDisabled(extn_with_switch_off.get(), &reason,
&error));
// Set the Extensions switch to ON. The extension should have been granted
// parent approval when the SkipParentApprovalToInstallExtension preference is
// flipped.
supervised_user_test_util::SetSkipParentApprovalToInstallExtensionsPref(
profile(), true);
bool is_extension_approved =
GetParam() == ExtensionsManagingToggle::kExtensions ? true : false;
EXPECT_EQ(is_extension_approved,
manager_->IsExtensionAllowed(*extn_with_switch_off.get()));
EXPECT_EQ(is_extension_approved,
!manager_->MustRemainDisabled(extn_with_switch_off.get(), &reason,
&error));
EXPECT_EQ(is_extension_approved,
profile()
->GetPrefs()
->GetDict(prefs::kSupervisedUserApprovedExtensions)
.contains(extn_with_switch_off->id()));
}
// Tests the local approval is revoked on uninstalling the extension or
// when the extension gains normal parental approval.
TEST_P(SupervisedUserExtensionsManagerTest, RevokeLocalApproval) {
ASSERT_TRUE(profile_->IsChild());
scoped_refptr<const Extension> locally_approved_extn1 =
MakeExtension("extension_test_1");
service()->AddExtension(locally_approved_extn1.get());
scoped_refptr<const Extension> locally_approved_extn2 =
MakeExtension("extension_test_2");
service()->AddExtension(locally_approved_extn2.get());
// Create the object under test.
MakeSupervisedUserExtensionsManager();
bool has_local_approval_migration_run = false;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
has_local_approval_migration_run =
GetParam() == ExtensionsManagingToggle::kExtensions;
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
auto* prefs = profile()->GetPrefs();
CHECK(prefs);
const base::Value::Dict& local_approved_extensions_pref =
prefs->GetDict(prefs::kSupervisedUserLocallyParentApprovedExtensions);
EXPECT_EQ(
has_local_approval_migration_run,
local_approved_extensions_pref.contains(locally_approved_extn1->id()));
EXPECT_EQ(
has_local_approval_migration_run,
registry()->enabled_extensions().Contains(locally_approved_extn1->id()));
// Uninstalling the extension also removes the local approval.
ASSERT_TRUE(service()->UninstallExtension(
locally_approved_extn1->id(), extensions::UNINSTALL_REASON_FOR_TESTING,
nullptr));
EXPECT_FALSE(
local_approved_extensions_pref.contains(locally_approved_extn1->id()));
// Granting parent approval (typically from another client) removes the local
// approval. The extension remains allowed.
manager_->AddExtensionApproval(*locally_approved_extn2.get());
EXPECT_FALSE(
local_approved_extensions_pref.contains(locally_approved_extn2->id()));
EXPECT_TRUE(manager_->IsExtensionAllowed(*locally_approved_extn2));
}
// Tests that on Desktop (Win/Linux/Mac) platforms, when the feature
// `kEnableSupervisedUserSkipParentApprovalToInstallExtensions` is first
// enabled, present extensions will be marked as locally parent-approved
// when a user profile becomes supervised.
// This covers the scenarios where:
// 1) supervision is applied to a previously unsupervised user
// 2) a supervised user signs-in on an existing profile that had installed
// extensions.
TEST_P(SupervisedUserExtensionsManagerTest,
MigrateExtensionsToLocallyApprovedOnUserBecomingSupervised) {
// Make the user non-supervised.
profile()->AsTestingProfile()->SetIsSupervisedProfile(false);
ASSERT_TRUE(!profile_->IsChild());
scoped_refptr<const Extension> locally_approved_extn =
MakeExtension("extension_test_2");
service()->AddExtension(locally_approved_extn.get());
// Create the object under test.
MakeSupervisedUserExtensionsManager();
auto* prefs = profile()->GetPrefs();
CHECK(prefs);
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
auto expected_migration_state = supervised_user::
LocallyParentApprovedExtensionsMigrationState::kNeedToRun;
EXPECT_EQ(
static_cast<int>(expected_migration_state),
prefs->GetInteger(prefs::kLocallyParentApprovedExtensionsMigrationState));
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
const base::Value::Dict& local_approved_extensions_pref =
prefs->GetDict(prefs::kSupervisedUserLocallyParentApprovedExtensions);
EXPECT_FALSE(
local_approved_extensions_pref.contains(locally_approved_extn->id()));
EXPECT_TRUE(
registry()->enabled_extensions().Contains(locally_approved_extn->id()));
// Make the user supervised. This should trigger the migration of extensions
// to locally-approved.
profile()->AsTestingProfile()->SetIsSupervisedProfile(true);
ASSERT_TRUE(profile_->IsChild());
bool has_local_approval_migration_run = false;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
expected_migration_state =
GetParam() == ExtensionsManagingToggle::kExtensions
? supervised_user::LocallyParentApprovedExtensionsMigrationState::
kComplete
: supervised_user::LocallyParentApprovedExtensionsMigrationState::
kNeedToRun;
has_local_approval_migration_run =
GetParam() == ExtensionsManagingToggle::kExtensions;
EXPECT_EQ(
static_cast<int>(expected_migration_state),
prefs->GetInteger(prefs::kLocallyParentApprovedExtensionsMigrationState));
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
const base::Value::Dict& local_approved_extensions_pref_post_migr =
prefs->GetDict(prefs::kSupervisedUserLocallyParentApprovedExtensions);
// The extensions approved in the migration should be allowed and part of the
// local-approved list.
EXPECT_EQ(has_local_approval_migration_run,
local_approved_extensions_pref_post_migr.contains(
locally_approved_extn->id()));
EXPECT_EQ(has_local_approval_migration_run,
manager_->IsExtensionAllowed(*locally_approved_extn));
EXPECT_EQ(
has_local_approval_migration_run,
registry()->enabled_extensions().Contains(locally_approved_extn->id()));
}
INSTANTIATE_TEST_SUITE_P(All,
SupervisedUserExtensionsManagerTest,
testing::Values(ExtensionsManagingToggle::kPermissions,
ExtensionsManagingToggle::kExtensions),
[](const auto& info) {
return std::string(
info.param ==
ExtensionsManagingToggle::kExtensions
? "ManagedByExtensions"
: "ManagedByPermissions");
});