blob: a305b553ac78f02cedf6e8f05c9b3194ffd5d2ea [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/account_extension_tracker.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/extensions/extension_sync_service.h"
#include "chrome/browser/extensions/extension_sync_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_change_event.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
namespace extensions {
namespace {
constexpr PrefMap kAccountExtensionTypePref = {"account_extension_type",
PrefType::kInteger,
PrefScope::kExtensionSpecific};
class AccountExtensionTrackerFactory : public ProfileKeyedServiceFactory {
public:
AccountExtensionTrackerFactory();
AccountExtensionTrackerFactory(const AccountExtensionTrackerFactory&) =
delete;
AccountExtensionTrackerFactory& operator=(
const AccountExtensionTrackerFactory&) = delete;
~AccountExtensionTrackerFactory() override = default;
AccountExtensionTracker* GetForBrowserContext(
content::BrowserContext* context);
private:
// BrowserContextKeyedServiceFactory:
std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const override;
bool ServiceIsCreatedWithBrowserContext() const override;
};
AccountExtensionTrackerFactory::AccountExtensionTrackerFactory()
: ProfileKeyedServiceFactory(
"AccountExtensionTracker",
ProfileSelections::Builder()
.WithRegular(ProfileSelection::kRedirectedToOriginal)
.WithGuest(ProfileSelection::kRedirectedToOriginal)
.WithAshInternals(ProfileSelection::kRedirectedToOriginal)
.Build()) {
DependsOn(ExtensionPrefsFactory::GetInstance());
DependsOn(ExtensionRegistryFactory::GetInstance());
DependsOn(IdentityManagerFactory::GetInstance());
}
AccountExtensionTracker* AccountExtensionTrackerFactory::GetForBrowserContext(
content::BrowserContext* browser_context) {
return static_cast<AccountExtensionTracker*>(
GetServiceForBrowserContext(browser_context, /*create=*/true));
}
std::unique_ptr<KeyedService>
AccountExtensionTrackerFactory::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
return std::make_unique<AccountExtensionTracker>(
Profile::FromBrowserContext(context));
}
bool AccountExtensionTrackerFactory::ServiceIsCreatedWithBrowserContext()
const {
return true;
}
} // namespace
AccountExtensionTracker::~AccountExtensionTracker() = default;
AccountExtensionTracker::AccountExtensionTracker(Profile* profile)
: profile_(profile) {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
identity_manager_observation_.Observe(identity_manager);
}
// static
AccountExtensionTracker* AccountExtensionTracker::Get(
content::BrowserContext* context) {
return static_cast<AccountExtensionTrackerFactory*>(GetFactory())
->GetForBrowserContext(context);
}
// static
BrowserContextKeyedServiceFactory* AccountExtensionTracker::GetFactory() {
static base::NoDestructor<AccountExtensionTrackerFactory> g_factory;
return g_factory.get();
}
void AccountExtensionTracker::SetAccountExtensionTypeOnExtensionInstalled(
const Extension& extension) {
bool extension_sync_enabled = sync_util::IsSyncingExtensionsEnabled(profile_);
bool is_syncable_extension = sync_util::ShouldSync(profile_, &extension);
// Set to `kAccountInstalledSignedIn` if this is a syncable extension (by
// ExtensionSyncService) that was installed when a user is signed in and has
// sync enabled. Otherwise, set to `kLocal`.
AccountExtensionType type =
(is_syncable_extension && extension_sync_enabled)
? AccountExtensionType::kAccountInstalledSignedIn
: AccountExtensionType::kLocal;
SetAccountExtensionType(extension.id(), type);
}
void AccountExtensionTracker::OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event_details) {
ExtensionRegistry* extension_registry = ExtensionRegistry::Get(profile_);
auto signin_event_type =
event_details.GetEventTypeFor(signin::ConsentLevel::kSignin);
switch (signin_event_type) {
case signin::PrimaryAccountChangeEvent::Type::kSet: {
// When the user has finished the signin flow initiated from an extension
// promo, promote all syncable extensions installed within the delay to
// account extensions.
for (const auto& extension_from_promo :
extensions_installed_with_signin_promo_) {
const Extension* extension =
extension_registry->GetInstalledExtension(extension_from_promo);
if (!extension) {
continue;
}
DCHECK(sync_util::ShouldSync(profile_, extension));
SetAccountExtensionType(
extension_from_promo,
AccountExtensionType::kAccountInstalledSignedIn);
}
extensions_installed_with_signin_promo_.clear();
break;
}
case signin::PrimaryAccountChangeEvent::Type::kCleared: {
// TODO(crbug.com/366474682): If extension syncing is enabled in transport
// mode, only set the pref if the user chooses to keep extensions when
// signing out.
const ExtensionSet extensions =
extension_registry->GenerateInstalledExtensionsSet();
for (const auto& extension : extensions) {
SetAccountExtensionType(extension->id(), AccountExtensionType::kLocal);
}
break;
}
case signin::PrimaryAccountChangeEvent::Type::kNone:
break;
}
}
void AccountExtensionTracker::OnExtensionSyncDataReceived(
const ExtensionId& extension_id) {
// Only change from `kLocal` to `kAccountInstalledLocally` since the existence
// of sync data for this extension implies it's associated with a signed in
// user.
// Extensions installed after sign in already have `kAccountInstalledSignedIn`
// and thus don't need to be set here.
AccountExtensionType type = GetAccountExtensionType(extension_id);
if (type == AccountExtensionType::kLocal) {
SetAccountExtensionType(extension_id,
AccountExtensionType::kAccountInstalledLocally);
}
}
AccountExtensionTracker::AccountExtensionType
AccountExtensionTracker::GetAccountExtensionType(
const ExtensionId& extension_id) const {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
int type_int = 0;
// If the pref does not exist or is corrupted (not a valid value), return
// `kLocal` as a fallback.
if (!prefs->ReadPrefAsInteger(extension_id, kAccountExtensionTypePref,
&type_int)) {
return AccountExtensionType::kLocal;
}
if (type_int < 0 || type_int > AccountExtensionType::kLast) {
return AccountExtensionType::kLocal;
}
return static_cast<AccountExtensionType>(type_int);
}
void AccountExtensionTracker::OnSignInInitiatedFromExtensionPromo(
const ExtensionId& extension_id) {
extensions_installed_with_signin_promo_.push_back(extension_id);
// Schedule a task to remove the `extension_id` from
// `extensions_installed_with_signin_promo_` after
// `kMaxSigninFromExtensionBubbleDelay`.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AccountExtensionTracker::RemoveExpiredExtension,
weak_factory_.GetWeakPtr(), extension_id),
kMaxSigninFromExtensionBubbleDelay);
}
bool AccountExtensionTracker::CanUploadAsAccountExtension(
const Extension& extension) const {
// Uploading extensions as "account extensions" aka extensions syncing to the
// current signed in user, is only enabled if the user is signed in and
// syncing extensions in transport mode.
if (!sync_util::IsSyncingExtensionsInTransportMode(profile_)) {
return false;
}
// An extension is eligible to be uploaded if it's syncable and is a local
// extension (i.e. it's not currently syncing).
return GetAccountExtensionType(extension.id()) ==
AccountExtensionType::kLocal &&
sync_util::ShouldSync(profile_, &extension);
}
void AccountExtensionTracker::SetAccountExtensionTypeForTesting(
const ExtensionId& extension_id,
AccountExtensionType type) {
SetAccountExtensionType(extension_id, type);
}
void AccountExtensionTracker::SetAccountExtensionType(
const ExtensionId& extension_id,
AccountExtensionTracker::AccountExtensionType type) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
prefs->SetIntegerPref(extension_id, kAccountExtensionTypePref, type);
}
void AccountExtensionTracker::RemoveExpiredExtension(
const ExtensionId& extension_id) {
std::erase_if(
extensions_installed_with_signin_promo_,
[&extension_id](const ExtensionId& id) { return extension_id == id; });
}
} // namespace extensions