| // 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); |
| } |
| } |
| } |