chromium/chrome/browser/lacros/account_manager/account_profile_mapper.cc

// Copyright 2021 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/lacros/account_manager/account_profile_mapper.h"

#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lacros/account_manager/add_account_helper.h"
#include "chrome/browser/profiles/delete_profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/chromeos/account_manager_facade_factory.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"

AccountProfileMapper::AccountProfileMapper(
    account_manager::AccountManagerFacade* facade,
    ProfileAttributesStorage* storage,
    PrefService* local_state)
    : account_manager_facade_(facade),
      profile_attributes_storage_(storage),
      account_cache_(local_state) {
  DCHECK(profile_attributes_storage_);

  // This must be done before OnGetAccountsCompleted() is called, to avoid
  // unnecessary profile deletion.
  MigrateOldProfiles();

  account_manager_facade_observation_.Observe(account_manager_facade_.get());
  profile_attributes_storage_observation_.Observe(
      profile_attributes_storage_.get());
  account_manager_facade_->GetAccounts(
      base::BindOnce(&AccountProfileMapper::OnGetAccountsCompleted,
                     weak_factory_.GetWeakPtr()));
}

AccountProfileMapper::~AccountProfileMapper() = default;

void AccountProfileMapper::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void AccountProfileMapper::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void AccountProfileMapper::GetAccounts(const base::FilePath& profile_path,
                                       ListAccountsCallback callback) {
  if (!initialized_) {
    initialization_callbacks_.push_back(base::BindOnce(
        &AccountProfileMapper::GetAccounts, weak_factory_.GetWeakPtr(),
        profile_path, std::move(callback)));
    return;
  }
  std::vector<account_manager::Account> accounts;
  const ProfileAttributesEntry* entry =
      profile_attributes_storage_->GetProfileAttributesWithPath(profile_path);
  // `entry` may be null during profile deletion, as it is destroyed before the
  // profile.
  if (entry) {
    for (const std::string& gaia_id : entry->GetGaiaIds()) {
      const account_manager::Account* account =
          account_cache_.FindAccountByGaiaId(gaia_id);
      if (!account) {
        NOTREACHED_IN_MIGRATION() << "Account " << gaia_id << " missing.";
        continue;
      }
      accounts.push_back(*account);
    }
  }
  std::move(callback).Run(accounts);
}

void AccountProfileMapper::GetPersistentErrorForAccount(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account,
    base::OnceCallback<void(const GoogleServiceAuthError&)> callback) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::GetPersistentErrorForAccount,
                       weak_factory_.GetWeakPtr(), profile_path, account,
                       std::move(callback)));
    return;
  }
  if (!ProfileContainsAccount(profile_path, account)) {
    std::move(callback).Run(
        GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP));
    return;
  }
  account_manager_facade_->GetPersistentErrorForAccount(account,
                                                        std::move(callback));
}

std::unique_ptr<OAuth2AccessTokenFetcher>
AccountProfileMapper::CreateAccessTokenFetcher(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account,
    OAuth2AccessTokenConsumer* consumer) {
  // TODO(crbug.com/40188699): Create a fetcher that can wait on
  // initialization of the class.
  if (!ProfileContainsAccount(profile_path, account)) {
    return std::make_unique<OAuth2AccessTokenFetcherImmediateError>(
        consumer,
        GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP));
  }

  return account_manager_facade_->CreateAccessTokenFetcher(account, consumer);
}

void AccountProfileMapper::ReportAuthError(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account,
    const GoogleServiceAuthError& error) {
  if (!initialized_) {
    initialization_callbacks_.push_back(base::BindOnce(
        &AccountProfileMapper::ReportAuthError, weak_factory_.GetWeakPtr(),
        profile_path, account, error));
    return;
  }

  if (!ProfileContainsAccount(profile_path, account))
    return;

  account_manager_facade_->ReportAuthError(account, error);
}

void AccountProfileMapper::GetAccountsMap(MapAccountsCallback callback) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::GetAccountsMap,
                       weak_factory_.GetWeakPtr(), std::move(callback)));
    return;
  }

  std::map<base::FilePath, std::vector<account_manager::Account>> accounts_map;
  AccountCache::AccountByGaiaIdMap unassigned_accounts =
      account_cache_.GetAccountsCopy();
  for (ProfileAttributesEntry* entry :
       profile_attributes_storage_->GetAllProfilesAttributes()) {
    const base::FilePath path = entry->GetPath();
    for (const std::string& gaia_id : entry->GetGaiaIds()) {
      const account_manager::Account* account =
          account_cache_.FindAccountByGaiaId(gaia_id);
      if (!account) {
        NOTREACHED_IN_MIGRATION() << "Account " << gaia_id << " missing.";
        continue;
      }
      accounts_map[path].push_back(*account);
      unassigned_accounts.erase(gaia_id);
    }
  }
  for (const auto& [gaia_id, account] : unassigned_accounts)
    accounts_map[base::FilePath()].push_back(account);
  std::move(callback).Run(accounts_map);
}

void AccountProfileMapper::ShowAddAccountDialog(
    const base::FilePath& profile_path,
    account_manager::AccountManagerFacade::AccountAdditionSource source,
    AddAccountCallback callback) {
  DCHECK(!profile_path.empty())
      << "For a new profile use ShowAddAccountDialogAndCreateNewProfile()";
  AddAccountInternal(profile_path, source, std::move(callback));
}

void AccountProfileMapper::AddAccount(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account,
    AddAccountCallback callback) {
  DCHECK(!profile_path.empty())
      << "For a new profile use CreateNewProfileWithAccount()";
  AddAccountInternal(profile_path, account, std::move(callback));
}

void AccountProfileMapper::ShowAddAccountDialogAndCreateNewProfile(
    account_manager::AccountManagerFacade::AccountAdditionSource source,
    AddAccountCallback callback) {
  AddAccountInternal(base::FilePath(), source, std::move(callback));
}

void AccountProfileMapper::CreateNewProfileWithAccount(
    const account_manager::AccountKey& account,
    AddAccountCallback callback) {
  AddAccountInternal(base::FilePath(), account, std::move(callback));
}

void AccountProfileMapper::RemoveAllAccounts(
    const base::FilePath& profile_path) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::RemoveAllAccounts,
                       weak_factory_.GetWeakPtr(), profile_path));
    return;
  }

  ProfileAttributesEntry* entry =
      profile_attributes_storage_->GetProfileAttributesWithPath(profile_path);

  if (!entry) {
    DLOG(ERROR) << "Profile with profile path: " << profile_path
                << " does not exist";
    return;
  }

  base::flat_set<std::string> gaia_ids = entry->GetGaiaIds();
  if (!gaia_ids.empty())
    RemoveAccountsInternal(profile_path, gaia_ids);
}

void AccountProfileMapper::RemoveAccount(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account_key) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::RemoveAccount,
                       weak_factory_.GetWeakPtr(), profile_path, account_key));
    return;
  }

  if (account_key.account_type() != account_manager::AccountType::kGaia)
    return;

  RemoveAccountsInternal(profile_path, {account_key.id()});
}

void AccountProfileMapper::OnAccountUpserted(
    const account_manager::Account& account) {
  if (account.key.account_type() != account_manager::AccountType::kGaia)
    return;

  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::OnAccountUpserted,
                       weak_factory_.GetWeakPtr(), account));
    return;
  }

  if (account_cache_.FindAccountByGaiaId(account.key.id())) {
    // The account is already known. This is an account update. Propagate the
    // update to all profiles that have this account.
    std::vector<ProfileAttributesEntry*> entries =
        profile_attributes_storage_->GetAllProfilesAttributes();
    std::vector<base::FilePath> profiles_with_updated_account;
    for (const ProfileAttributesEntry* entry : entries) {
      if (entry->GetGaiaIds().contains(account.key.id()))
        profiles_with_updated_account.push_back(entry->GetPath());
    }

    // Send a notification with empty path if `account` is unassigned.
    if (profiles_with_updated_account.empty())
      profiles_with_updated_account.push_back(base::FilePath());

    for (const base::FilePath& profile_path : profiles_with_updated_account) {
      for (auto& obs : observers_)
        obs.OnAccountUpserted(profile_path, account);
    }

    // Do not return early to update the list of accounts below.
    // `account_cache_` might be stale here. There is a small chance that
    // `account` has been already removed and re-added and that we took re-add
    // operation for an update.
  }

  account_manager_facade_->GetAccounts(
      base::BindOnce(&AccountProfileMapper::OnGetAccountsCompleted,
                     weak_factory_.GetWeakPtr()));
}

void AccountProfileMapper::OnAccountRemoved(
    const account_manager::Account& account) {
  if (account.key.account_type() != account_manager::AccountType::kGaia)
    return;

  account_manager_facade_->GetAccounts(
      base::BindOnce(&AccountProfileMapper::OnGetAccountsCompleted,
                     weak_factory_.GetWeakPtr()));
}

void AccountProfileMapper::OnAuthErrorChanged(
    const account_manager::AccountKey& account,
    const GoogleServiceAuthError& error) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::OnAuthErrorChanged,
                       weak_factory_.GetWeakPtr(), account, error));
    return;
  }

  DCHECK_EQ(account.account_type(), account_manager::AccountType::kGaia);
  if (!account_cache_.FindAccountByGaiaId(account.id())) {
    LOG(ERROR) << "Ignoring account error update for unknown account";
    return;
  }

  std::vector<ProfileAttributesEntry*> entries =
      profile_attributes_storage_->GetAllProfilesAttributes();
  for (const ProfileAttributesEntry* entry : entries) {
    if (!entry->GetGaiaIds().contains(account.id())) {
      continue;
    }

    for (auto& observer : observers_) {
      observer.OnAuthErrorChanged(entry->GetPath(), account, error);
    }
  }
}

void AccountProfileMapper::OnProfileWillBeRemoved(
    const base::FilePath& profile_path) {
  ProfileAttributesEntry* entry_to_be_removed =
      profile_attributes_storage_->GetProfileAttributesWithPath(profile_path);
  DCHECK(entry_to_be_removed);

  // Compute a set of accounts that are assigned to at least one profile.
  base::flat_set<std::string> assigned_account_ids;
  for (ProfileAttributesEntry* entry :
       profile_attributes_storage_->GetAllProfilesAttributes()) {
    if (entry == entry_to_be_removed)
      continue;
    base::flat_set<std::string> profile_account_ids = entry->GetGaiaIds();
    assigned_account_ids.insert(profile_account_ids.begin(),
                                profile_account_ids.end());
  }

  // Compute a list of accounts that will become unassigned after a profile at
  // `profile_path` is removed.
  base::flat_set<std::string> freed_account_ids =
      entry_to_be_removed->GetGaiaIds();
  std::vector<std::string> unassigned_account_ids;
  base::ranges::set_difference(freed_account_ids, assigned_account_ids,
                               std::back_inserter(unassigned_account_ids));

  // Notify observers about accounts that became unassigned.
  for (const std::string& unassigned_account_id : unassigned_account_ids) {
    const account_manager::Account* account =
        account_cache_.FindAccountByGaiaId(unassigned_account_id);
    // `account_cache_` might be outdated.
    if (!account)
      continue;

    for (auto& obs : observers_)
      obs.OnAccountRemoved(profile_path, *account);
  }
}

void AccountProfileMapper::UpsertAccountForTesting(
    const base::FilePath& profile_path,
    const account_manager::Account& account,
    const std::string& token_value) {
  if (!initialized_) {
    initialization_callbacks_.push_back(base::BindOnce(
        &AccountProfileMapper::UpsertAccountForTesting,
        weak_factory_.GetWeakPtr(), profile_path, account, token_value));
    return;
  }

  add_account_helpers_.push_back(std::make_unique<AddAccountHelper>(
      base::BindRepeating(
          &AccountProfileMapper::IsAccountInCache,
          // `this` owns the helper, so base::Unretained() is fine.
          base::Unretained(this)),
      account_manager_facade_, profile_attributes_storage_));
  AddAccountHelper* helper = add_account_helpers_.back().get();

  helper->UpsertAccountForTesting(  // IN-TEST
      profile_path, account, token_value,
      base::BindOnce(&AccountProfileMapper::OnAddAccountCompleted,
                     weak_factory_.GetWeakPtr(), helper, base::DoNothing()));
}

void AccountProfileMapper::RemoveAccountForTesting(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account_key) {
  if (!initialized_) {
    initialization_callbacks_.push_back(
        base::BindOnce(&AccountProfileMapper::RemoveAccountForTesting,
                       weak_factory_.GetWeakPtr(), profile_path, account_key));
    return;
  }
  account_manager_facade_->RemoveAccountForTesting(account_key);  // IN-TEST
}

bool AccountProfileMapper::IsAccountInCache(
    const account_manager::Account& account) {
  return account.key.account_type() == account_manager::AccountType::kGaia &&
         account_cache_.FindAccountByGaiaId(account.key.id());
}

void AccountProfileMapper::AddAccountInternal(
    const base::FilePath& profile_path,
    const absl::variant<
        account_manager::AccountManagerFacade::AccountAdditionSource,
        account_manager::AccountKey>& source_or_accountkey,
    AddAccountCallback callback) {
  if (!initialized_) {
    initialization_callbacks_.push_back(base::BindOnce(
        &AccountProfileMapper::AddAccountInternal, weak_factory_.GetWeakPtr(),
        profile_path, source_or_accountkey, std::move(callback)));
    return;
  }

  absl::variant<account_manager::AccountManagerFacade::AccountAdditionSource,
                account_manager::Account>
      source_or_account;
  // If an account is provided, check that it exists in the facade.
  if (const account_manager::AccountKey* account_key =
          absl::get_if<account_manager::AccountKey>(&source_or_accountkey)) {
    if (account_key->account_type() != account_manager::AccountType::kGaia) {
      if (callback)
        std::move(callback).Run(std::nullopt);
      return;
    }
    const account_manager::Account* account =
        account_cache_.FindAccountByGaiaId(account_key->id());
    if (!account) {
      if (callback)
        std::move(callback).Run(std::nullopt);
      return;
    } else {
      source_or_account = *account;
    }
  } else {
    source_or_account =
        absl::get<account_manager::AccountManagerFacade::AccountAdditionSource>(
            source_or_accountkey);
  }

  add_account_helpers_.push_back(std::make_unique<AddAccountHelper>(
      base::BindRepeating(
          &AccountProfileMapper::IsAccountInCache,
          // `this` owns the helper, so base::Unretained() is fine.
          base::Unretained(this)),
      account_manager_facade_, profile_attributes_storage_));
  AddAccountHelper* helper = add_account_helpers_.back().get();
  helper->Start(
      profile_path, source_or_account,
      base::BindOnce(&AccountProfileMapper::OnAddAccountCompleted,
                     weak_factory_.GetWeakPtr(), helper, std::move(callback)));
}

void AccountProfileMapper::OnAddAccountCompleted(
    AddAccountHelper* helper,
    AddAccountCallback callback,
    const std::optional<AddAccountResult>& result) {
  // Note: the new account may or may not be in `account_cache_`. There is a
  // small possibility that an account was already removed from the OS. As a
  // result, this function does not use `account_cache_` at all.
  // Exclude unassigned accounts because `OnGetAccountsCompleted()` will notify
  // observers about them.
  if (result && !result->profile_path.empty()) {
    for (auto& obs : observers_)
      obs.OnAccountUpserted(result->profile_path, result->account);
  }

  if (callback)
    std::move(callback).Run(result);

  size_t erased_count =
      std::erase_if(add_account_helpers_, base::MatchesUniquePtr(helper));
  DCHECK_EQ(erased_count, 1u);
  if (add_account_helpers_.empty()) {
    account_manager_facade_->GetAccounts(
        base::BindOnce(&AccountProfileMapper::OnGetAccountsCompleted,
                       weak_factory_.GetWeakPtr()));
  }
}

std::vector<std::pair<base::FilePath, std::string>>
AccountProfileMapper::RemoveStaleAccounts() {
  std::vector<std::pair<base::FilePath, std::string>> removed_ids;
  std::vector<ProfileAttributesEntry*> entries =
      profile_attributes_storage_->GetAllProfilesAttributes();
  // For each profile.
  for (ProfileAttributesEntry* entry : entries) {
    base::flat_set<std::string> entry_ids = entry->GetGaiaIds();
    bool entry_needs_update = false;
    // For each account in the profile.
    auto it = entry_ids.begin();
    while (it != entry_ids.end()) {
      if (account_cache_.FindAccountByGaiaId(*it)) {
        ++it;
      } else {
        // An account in the profile is no longer in the system.
        entry_needs_update = true;
        removed_ids.push_back(std::make_pair(entry->GetPath(), *it));
        it = entry_ids.erase(it);
      }
    }
    if (entry_needs_update)
      entry->SetGaiaIds(entry_ids);
    if (ShouldDeleteProfile(entry)) {
      // Pass an empty callback because this should never delete the last
      // profile.
      // TODO(crbug.com/40200752): ensure that the user cannot cancel the
      // profile deletion.
      g_browser_process->profile_manager()
          ->GetDeleteProfileHelper()
          .MaybeScheduleProfileForDeletion(
              entry->GetPath(), base::DoNothing(),
              ProfileMetrics::DELETE_PROFILE_PRIMARY_ACCOUNT_REMOVED_LACROS);
    }
  }
  return removed_ids;
}

std::vector<const account_manager::Account*>
AccountProfileMapper::AddNewGaiaAccounts(
    const std::vector<account_manager::Account>& system_accounts,
    AccountCache::AccountIdSet lacros_account_ids,
    ProfileAttributesEntry* entry_for_new_accounts) {
  // Add the set of Gaia IDs in the profiles to `lacros_account_ids`. This is
  // important because accounts created by Chrome are initially added to
  // ProfileAttributesStorage first, but not to account cache. Otherwise, we'd
  // treat these new accounts as new unassigned accounts.
  std::vector<ProfileAttributesEntry*> entries =
      profile_attributes_storage_->GetAllProfilesAttributes();
  for (ProfileAttributesEntry* entry : entries) {
    base::flat_set<std::string> entry_ids = entry->GetGaiaIds();
    lacros_account_ids.insert(entry_ids.begin(), entry_ids.end());
  }

  // Diff computed set against system accounts.
  std::vector<const account_manager::Account*> added_accounts;
  for (const account_manager::Account& account : system_accounts) {
    if (account.key.account_type() == account_manager::AccountType::kGaia &&
        !lacros_account_ids.contains(account.key.id())) {
      added_accounts.push_back(&account);
    }
  }
  // Update the `ProfileAttributesEntry`.
  if (entry_for_new_accounts) {
    base::flat_set<std::string> gaia_ids = entry_for_new_accounts->GetGaiaIds();
    for (const account_manager::Account* account : added_accounts)
      gaia_ids.insert(account->key.id());
    entry_for_new_accounts->SetGaiaIds(gaia_ids);
  }
  return added_accounts;
}

void AccountProfileMapper::OnGetAccountsCompleted(
    const std::vector<account_manager::Account>& system_accounts) {
  account_cache_.UpdateAccounts(system_accounts);

  // `AccountManagerFacade` may call `OnAccountUpserted()` before the
  // `ShowAddAccountDialog()` callback, which may result in this function being
  // called during the account addition. In this case, notify helpers and
  // return immediately now, to avoid incorrectly assigning accounts to the main
  // profile. Another call will be triggered at the end of the account addition
  // to sort things up.
  if (!add_account_helpers_.empty()) {
    // Create a local copy of `add_account_helpers_` because
    // `OnAccountCacheUpdated()` may delete a helper in the middle of the
    // iteration.
    std::vector<AddAccountHelper*> local_add_account_helpers;
    local_add_account_helpers.reserve(add_account_helpers_.size());
    for (const auto& add_account_helper : add_account_helpers_)
      local_add_account_helpers.push_back(add_account_helper.get());

    for (auto* add_account_helper : local_add_account_helpers)
      add_account_helper->OnAccountCacheUpdated();
    return;
  }

  // Accounts that were removed.
  std::vector<std::pair<base::FilePath, std::string>> removed_ids =
      RemoveStaleAccounts();
  ProfileAttributesEntry* entry_for_new_accounts =
      MaybeGetProfileForNewAccounts();

  if (initialized_) {
    DCHECK(initialization_callbacks_.empty());

    AccountCache::AccountByGaiaIdMap old_cache =
        account_cache_.UpdateSnapshot();
    AccountCache::AccountIdSet old_account_ids = base::MakeFlatSet<std::string>(
        old_cache, {},
        [](const auto& mapped_pair) { return mapped_pair.first; });
    // Accounts that were added.
    std::vector<const account_manager::Account*> added_accounts =
        AddNewGaiaAccounts(system_accounts, std::move(old_account_ids),
                           entry_for_new_accounts);

    // Call observers once all entries are updated.
    base::FilePath path_for_new_accounts;
    if (entry_for_new_accounts)
      path_for_new_accounts = entry_for_new_accounts->GetPath();
    // Accounts added (either to a profile or unassigned).
    for (const auto* account : added_accounts) {
      DCHECK_EQ(account->key.account_type(),
                account_manager::AccountType::kGaia);
      for (auto& obs : observers_)
        obs.OnAccountUpserted(path_for_new_accounts, *account);
    }
    // Accounts removed that were assigned to a profile: pass the profile path.
    base::flat_set<std::string> removed_accounts_notified;
    for (const auto& [profile_path, gaia_id] : removed_ids) {
      auto it = old_cache.find(gaia_id);
      if (it == old_cache.cend()) {
        NOTREACHED_IN_MIGRATION() << "Account " << gaia_id << " missing.";
        continue;
      }
      removed_accounts_notified.insert(gaia_id);
      for (auto& obs : observers_)
        obs.OnAccountRemoved(profile_path, it->second);
    }
    // Unassigned accounts removed: pass the empty path.
    for (const auto& [gaia_id, account] : old_cache) {
      if (!base::Contains(system_accounts, account) &&
          !removed_accounts_notified.contains(account.key.id())) {
        for (auto& obs : observers_)
          obs.OnAccountRemoved(base::FilePath(), account);
      }
    }
  } else {
    AccountCache::AccountIdSet old_account_ids =
        account_cache_.CreateSnapshot();
    AddNewGaiaAccounts(system_accounts, std::move(old_account_ids),
                       entry_for_new_accounts);
    initialized_ = true;
    for (auto& callback : initialization_callbacks_)
      std::move(callback).Run();
    initialization_callbacks_.clear();
  }
}

bool AccountProfileMapper::ProfileContainsAccount(
    const base::FilePath& profile_path,
    const account_manager::AccountKey& account) const {
  const ProfileAttributesEntry* entry =
      profile_attributes_storage_->GetProfileAttributesWithPath(profile_path);
  if (!entry)
    return false;
  return entry->GetGaiaIds().contains(account.id());
}

ProfileAttributesEntry* AccountProfileMapper::MaybeGetProfileForNewAccounts()
    const {
  std::vector<ProfileAttributesEntry*> entries =
      profile_attributes_storage_->GetAllProfilesAttributes();
  // Ignore omitted profiles.
  std::erase_if(entries,
                [](const auto* entry) -> bool { return entry->IsOmitted(); });
  if (entries.empty())
    return nullptr;  // Happens in tests.
  // If there are multiple profiles, leave new accounts unassigned.
  if (entries.size() > 1)
    return nullptr;
  // Otherwise auto-assign new accounts to the main profile.
  DCHECK(Profile::IsMainProfilePath(entries[0]->GetPath()));
  return entries[0];
}

bool AccountProfileMapper::ShouldDeleteProfile(
    ProfileAttributesEntry* entry) const {
  // Delete profile if its primary account has been removed.
  const std::string& primary_gaia_id = entry->GetGAIAId();
  bool primary_account_deleted =
      !primary_gaia_id.empty() &&
      !account_cache_.FindAccountByGaiaId(primary_gaia_id);

  if (Profile::IsMainProfilePath(entry->GetPath())) {
    // Never delete the main profile.
    if (primary_account_deleted && !MaybeGetAshAccountManagerForTests()) {
      // Primary account of the main profile must never be deleted. A CHECK here
      // can possibly put a device in a crash loop, so upload a crash report
      // silently instead.
      // Do not emit these in tests, as some tests don't have an account in the
      // main profile, in particular if they are shared with desktop platforms.
      DLOG(ERROR) << "Primary account has been removed from the main profile";
      base::debug::DumpWithoutCrashing();
    }
    return false;
  }

  // If non syncing profiles enabled, only delete syncing managed profile.
  // For non managed profiles, the SigninManager will signout the profile if
  // there is no refresh token for the primary account as soon as the profile is
  // loaded. The profile may not signout if the account has been re-added to the
  // OS and `MigrateOldProfiles` re-inserted the Gaia Id before the profile is
  // loaded.
  return primary_account_deleted && entry->IsAuthenticated() &&
         AccountInfo::IsManaged(entry->GetHostedDomain());
}

void AccountProfileMapper::MigrateOldProfiles() {
  for (ProfileAttributesEntry* entry :
       profile_attributes_storage_->GetAllProfilesAttributes()) {
    // Populate missing Gaia Ids.
    // Note: this code might re-insert the Gaia id of a profile that has not
    // been loaded since its primary account removal from the OS (AuthInfo did
    // not re-set). The account will be removed later if it does not exist in
    // the OS in `RemoveStaleAccounts`.
    std::string primary_gaia_id = entry->GetGAIAId();
    if (!primary_gaia_id.empty()) {
      base::flat_set<std::string> gaia_ids = entry->GetGaiaIds();
      auto inserted_result = gaia_ids.insert(primary_gaia_id);
      if (inserted_result.second)
        entry->SetGaiaIds(gaia_ids);
    }
  }
}

void AccountProfileMapper::RemoveAccountsInternal(
    const base::FilePath& profile_path,
    const base::flat_set<std::string>& gaia_ids) {
  // Note: On initialization 'RemoveAllAccounts' may run before or after
  // `ProfileOAuth2TokenServiceDelegateChromeOS::OnGetAccounts()` which will
  // influence the order of `OnRefreshTokenRevoked()` and
  // `OnRefreshTokensLoaded()`. The'ProfileOAuth2TokenServiceDelegateChromeOS'
  // ensures the integrity of accounts with refresh tokens through
  // 'pending_accounts_'.
  DCHECK(!profile_path.empty());
  DCHECK(initialized_);
  DCHECK(!gaia_ids.empty());

  // Profile might be deleted during initialization.
  ProfileAttributesEntry* entry =
      profile_attributes_storage_->GetProfileAttributesWithPath(profile_path);

  if (!entry) {
    DLOG(ERROR) << "Profile with profile path: " << profile_path
                << " does not exist";
    return;
  }

  if (Profile::IsMainProfilePath(profile_path) &&
      gaia_ids.contains(entry->GetGAIAId())) {
    DLOG(ERROR)
        << "The primary account should not be removed from the main profile";
    return;
  }

  std::vector<account_manager::Account> removed_accounts;
  base::flat_set<std::string> profile_accounts = entry->GetGaiaIds();
  for (const auto& gaia_id : gaia_ids) {
    if (profile_accounts.contains(gaia_id)) {
      profile_accounts.erase(gaia_id);
      const account_manager::Account* account =
          account_cache_.FindAccountByGaiaId(gaia_id);
      if (account)
        removed_accounts.push_back(*account);
      else
        DLOG(ERROR) << "Account " << gaia_id << " missing.";
    }
  }
  entry->SetGaiaIds(profile_accounts);

  for (auto& obs : observers_) {
    for (const account_manager::Account& account : removed_accounts) {
      obs.OnAccountRemoved(profile_path, account);
    }
  }
}