diff options
Diffstat (limited to 'chromium/chrome/browser/signin/signin_manager.cc')
-rw-r--r-- | chromium/chrome/browser/signin/signin_manager.cc | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/chromium/chrome/browser/signin/signin_manager.cc b/chromium/chrome/browser/signin/signin_manager.cc new file mode 100644 index 00000000000..9cc73e0e49e --- /dev/null +++ b/chromium/chrome/browser/signin/signin_manager.cc @@ -0,0 +1,200 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/signin/signin_manager.h" + +#include "components/prefs/pref_service.h" +#include "components/signin/public/base/signin_pref_names.h" +#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" +#include "components/signin/public/identity_manager/identity_manager.h" +#include "components/signin/public/identity_manager/primary_account_mutator.h" + +SigninManager::SigninManager(PrefService* prefs, + signin::IdentityManager* identity_manager) + : prefs_(prefs), identity_manager_(identity_manager) { + signin_allowed_.Init( + prefs::kSigninAllowed, prefs_, + base::BindRepeating(&SigninManager::OnSigninAllowedPrefChanged, + base::Unretained(this))); + + UpdateUnconsentedPrimaryAccount(); + identity_manager_->AddObserver(this); +} + +SigninManager::~SigninManager() { + identity_manager_->RemoveObserver(this); +} + +void SigninManager::UpdateUnconsentedPrimaryAccount() { + // Only update the unconsented primary account only after accounts are loaded. + if (!identity_manager_->AreRefreshTokensLoaded()) { + return; + } + + absl::optional<CoreAccountInfo> account = + ComputeUnconsentedPrimaryAccountInfo(); + + DCHECK(!account || !account->IsEmpty()); + if (account) { + if (identity_manager_->GetPrimaryAccountInfo( + signin::ConsentLevel::kSignin) != account) { + DCHECK( + !identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)); + identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount( + account->account_id, signin::ConsentLevel::kSignin); + } + } else if (identity_manager_->HasPrimaryAccount( + signin::ConsentLevel::kSignin)) { + DCHECK(!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)); + identity_manager_->GetPrimaryAccountMutator()->ClearPrimaryAccount( + signin_metrics::USER_DELETED_ACCOUNT_COOKIES, + signin_metrics::SignoutDelete::kIgnoreMetric); + } +} + +absl::optional<CoreAccountInfo> +SigninManager::ComputeUnconsentedPrimaryAccountInfo() const { + DCHECK(identity_manager_->AreRefreshTokensLoaded()); + + // UPA is equal to the primary account with sync consent if it exists. + if (identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) { + return identity_manager_->GetPrimaryAccountInfo( + signin::ConsentLevel::kSync); + } + + // Clearing the primary sync account when sign-in is not allowed is handled + // by PrimaryAccountPolicyManager. That flow is extremely hard to follow + // especially for the case when the user is syncing with a managed account + // as in that case the whole profile needs to be deleted. + // + // It was considered simpler to keep the logic to update the unconsented + // primary account in a single place. + if (!signin_allowed_.GetValue()) + return absl::nullopt; + + signin::AccountsInCookieJarInfo cookie_info = + identity_manager_->GetAccountsInCookieJar(); + + std::vector<gaia::ListedAccount> cookie_accounts = + cookie_info.signed_in_accounts; + + // Fresh cookies and loaded tokens are needed to compute the UPA. + if (cookie_info.accounts_are_fresh) { + // Cookies are fresh and tokens are loaded, UPA is the first account + // in cookies if it exists and has a refresh token. + if (cookie_accounts.empty()) { + // Cookies are empty, the UPA is empty. + return absl::nullopt; + } + + AccountInfo account_info = + identity_manager_->FindExtendedAccountInfoByAccountId( + cookie_accounts[0].id); + + // Verify the first account in cookies has a refresh token that is valid. + bool error_state = + account_info.IsEmpty() || + identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + account_info.account_id); + + return error_state ? absl::nullopt + : absl::make_optional<CoreAccountInfo>(account_info); + } + + if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) + return absl::nullopt; + + // If cookies or tokens are not loaded, it is not possible to fully compute + // the unconsented primary account. However, if the current unconsented + // primary account is no longer valid, it has to be removed. + CoreAccountId current_account = + identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin); + + if (!identity_manager_->HasAccountWithRefreshToken(current_account)) { + // Tokens are loaded, but the current UPA doesn't have a refresh token. + // Clear the current UPA. + return absl::nullopt; + } + + if (cookie_info.accounts_are_fresh) { + if (cookie_accounts.empty() || cookie_accounts[0].id != current_account) { + // The current UPA is not the first in fresh cookies. It needs to be + // cleared. + return absl::nullopt; + } + } + + // No indication that the current UPA is invalid, return current UPA. + return identity_manager_->GetPrimaryAccountInfo( + signin::ConsentLevel::kSignin); +} + +// signin::IdentityManager::Observer implementation. +void SigninManager::OnPrimaryAccountChanged( + const signin::PrimaryAccountChangeEvent& event_details) { + // This is needed for the case where the user chooses to start syncing + // with an account that is different from the unconsented primary account + // (not the first in cookies) but then cancels. In that case, the tokens stay + // the same. In all the other cases, either the token will be revoked which + // will trigger an update for the unconsented primary account or the + // primary account stays the same but the sync consent is revoked. + if (event_details.GetEventTypeFor(signin::ConsentLevel::kSync) != + signin::PrimaryAccountChangeEvent::Type::kCleared) { + return; + } + + // It is important to update the primary account after all observers process + // the current OnPrimaryAccountChanged() as all observers should see the same + // value for the unconsented primary account. Schedule the potential update + // on the next run loop. + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&SigninManager::UpdateUnconsentedPrimaryAccount, + weak_ptr_factory_.GetWeakPtr())); +} + +void SigninManager::OnRefreshTokenUpdatedForAccount( + const CoreAccountInfo& account_info) { + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnRefreshTokenRemovedForAccount( + const CoreAccountId& account_id) { + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnRefreshTokensLoaded() { + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnAccountsInCookieUpdated( + const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, + const GoogleServiceAuthError& error) { + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnAccountsCookieDeletedByUserAction() { + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnErrorStateOfRefreshTokenUpdatedForAccount( + const CoreAccountInfo& account_info, + const GoogleServiceAuthError& error) { + CoreAccountInfo current_account = + identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin); + + bool should_update = false; + if (error == GoogleServiceAuthError::AuthErrorNone()) { + should_update = current_account.IsEmpty(); + } else { + // In error state, update if the account in error is the current UPA. + should_update = (account_info == current_account); + } + + if (should_update) + UpdateUnconsentedPrimaryAccount(); +} + +void SigninManager::OnSigninAllowedPrefChanged() { + UpdateUnconsentedPrimaryAccount(); +} |