blob: c987c5cd52bc596d23950b6075692baf17239964 [file] [log] [blame]
// Copyright 2024 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/extensions/sync/account_extension_tracker.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_with_install.h"
#include "chrome/browser/extensions/signin_test_util.h"
#include "chrome/browser/extensions/sync/extension_sync_service.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
namespace extensions {
namespace {
constexpr char kGoodCrx[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
} // namespace
class AccountExtensionTrackerUnitTest : public ExtensionServiceTestWithInstall {
public:
void SetUp() override {
ExtensionServiceTestWithInstall::SetUp();
InitializeEmptyExtensionService();
service()->Init();
ASSERT_TRUE(extension_system()->is_ready());
identity_test_env_profile_adaptor_ =
std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
}
protected:
ExtensionSystem* extension_system() {
return ExtensionSystem::Get(profile());
}
AccountExtensionTracker::AccountExtensionType GetAccountExtensionType(
const ExtensionId& id) {
return AccountExtensionTracker::Get(profile())->GetAccountExtensionType(id);
}
signin::IdentityTestEnvironment* identity_test_env() {
return identity_test_env_profile_adaptor_->identity_test_env();
}
private:
std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
identity_test_env_profile_adaptor_;
};
// Test that an extension's AccountExtensionType is set to the right value based
// on whether it was installed when there is a signed in user with sync enabled.
TEST_F(AccountExtensionTrackerUnitTest, AccountExtensionTypeSignedIn) {
base::FilePath good_crx_path = data_dir().AppendASCII("good.crx");
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
GetAccountExtensionType(kGoodCrx));
UninstallExtension(kGoodCrx);
// Mimic signing a user in with sync enabled.
identity_test_env()->MakePrimaryAccountAvailable("testy@mctestface.com",
signin::ConsentLevel::kSync);
// Reinstall `kGoodCrx` while there is a signed in user. Since `kGoodCrx` is
// syncable, it should be associated with the user's account data, and since
// it was installed when there is a signed in user, its AccountExtensionType
// is `kAccountInstalledSignedIn`.
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(
AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
GetAccountExtensionType(kGoodCrx));
// Install an external extension. This extension shouldn't be attached to
// account data due to being unsyncable and thus has the AccountExtensionType
// `kLocal`.
ChromeTestExtensionLoader extension_loader(profile());
extension_loader.set_pack_extension(true);
extension_loader.add_creation_flag(Extension::WAS_INSTALLED_BY_DEFAULT);
scoped_refptr<const Extension> external_extension =
extension_loader.LoadExtension(
data_dir().AppendASCII("simple_with_file"));
ASSERT_TRUE(external_extension);
EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
GetAccountExtensionType(external_extension->id()));
}
// Same as the above test, except this uses transport mode (signed in but not
// syncing) instead of sync, and an explicit user sign in.
TEST_F(AccountExtensionTrackerUnitTest, AccountExtensionTypeTransportMode) {
// Enable extension syncing in transport mode.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
switches::kEnableExtensionsExplicitBrowserSignin);
// The extension's AccountExtensionType is `kLocal` because the user has not
// explicitly signed in yet.
base::FilePath good_crx_path = data_dir().AppendASCII("good.crx");
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
GetAccountExtensionType(kGoodCrx));
UninstallExtension(kGoodCrx);
signin_test_util::SimulateExplicitSignIn(profile(), identity_test_env());
// Reinstall the extension. Since the user has now signed in properly. the
// extension should be associated with the user's account data and have an
// AccountExtensionType of `kAccountInstalledSignedIn`.
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(
AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
GetAccountExtensionType(kGoodCrx));
// Install an external extension. This extension shouldn't be attached to
// account data due to being unsyncable and thus has the AccountExtensionType
// `kLocal`.
ChromeTestExtensionLoader extension_loader(profile());
extension_loader.set_pack_extension(true);
extension_loader.add_creation_flag(Extension::WAS_INSTALLED_BY_DEFAULT);
scoped_refptr<const Extension> external_extension =
extension_loader.LoadExtension(
data_dir().AppendASCII("simple_with_file"));
ASSERT_TRUE(external_extension);
EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
GetAccountExtensionType(external_extension->id()));
}
#if !BUILDFLAG(IS_CHROMEOS)
TEST_F(AccountExtensionTrackerUnitTest,
AccountExtensionTypeResetWhenSignedOut) {
// Enable extension syncing in transport mode.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
switches::kEnableExtensionsExplicitBrowserSignin);
signin_test_util::SimulateExplicitSignIn(profile(), identity_test_env());
base::FilePath good_crx_path = data_dir().AppendASCII("good.crx");
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(
AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
GetAccountExtensionType(kGoodCrx));
// Sign the user out and verify that `kGoodCrx` is now treated as a local
// extension again.
identity_test_env()->ClearPrimaryAccount();
EXPECT_TRUE(registry()->GetInstalledExtension(kGoodCrx));
EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
GetAccountExtensionType(kGoodCrx));
}
TEST_F(AccountExtensionTrackerUnitTest, AccountExtensionsRemovedWhenSignedOut) {
// Enable extension syncing in transport mode.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
switches::kEnableExtensionsExplicitBrowserSignin);
signin_test_util::SimulateExplicitSignIn(profile(), identity_test_env());
base::FilePath good_crx_path = data_dir().AppendASCII("good.crx");
InstallCRX(good_crx_path, INSTALL_NEW);
EXPECT_EQ(
AccountExtensionTracker::AccountExtensionType::kAccountInstalledSignedIn,
GetAccountExtensionType(kGoodCrx));
// Install an extension and pretend it's a `kAccountInstalledSignedIn`
// extension.
ChromeTestExtensionLoader extension_loader(profile());
extension_loader.set_pack_extension(true);
scoped_refptr<const Extension> other_extension =
extension_loader.LoadExtension(
data_dir().AppendASCII("simple_with_file"));
const ExtensionId other_extension_id = other_extension->id();
AccountExtensionTracker::Get(profile())->SetAccountExtensionTypeForTesting(
other_extension_id,
AccountExtensionTracker::AccountExtensionType::kAccountInstalledLocally);
// Set the uninstall flag.
AccountExtensionTracker::Get(profile())
->set_uninstall_account_extensions_on_signout(true);
// Sign the user out and verify that `kGoodCrx` is now uninstalled.
identity_test_env()->ClearPrimaryAccount();
EXPECT_FALSE(registry()->GetInstalledExtension(kGoodCrx));
// But `other_extension` is still installed.
EXPECT_TRUE(registry()->GetInstalledExtension(other_extension_id));
}
#endif // !BUILDFLAG(IS_CHROMEOS)
} // namespace extensions