| // Copyright 2015 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/mutable_profile_oauth2_token_service_delegate.h" |
| |
| #include <stddef.h> |
| |
| #include <map> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/signin_client.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #include "components/signin/core/browser/signin_pref_names.h" |
| #include "components/signin/core/browser/webdata/token_web_data.h" |
| #include "components/webdata/common/web_data_service_base.h" |
| #include "google_apis/gaia/gaia_auth_fetcher.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h" |
| #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace { |
| |
| const char kAccountIdPrefix[] = "AccountId-"; |
| const size_t kAccountIdPrefixLength = 10; |
| |
| // Used to record token state transitions in histograms. |
| // Do not change existing values, new values can only be added at the end. |
| enum class TokenStateTransition { |
| // Update events. |
| kNoneToInvalid = 0, |
| kNoneToRegular, |
| kInvalidToRegular, |
| kRegularToInvalid, |
| kRegularToRegular, |
| |
| // Revocation events. |
| kInvalidToNone, |
| kRegularToNone, |
| |
| // Load events. |
| kLoadRegular, |
| kLoadInvalid, |
| |
| kCount |
| }; |
| |
| // Enum for the Signin.LoadTokenFromDB histogram. |
| // Do not modify, or add or delete other than directly before |
| // NUM_LOAD_TOKEN_FROM_DB_STATUS. |
| enum class LoadTokenFromDBStatus { |
| // Token was loaded. |
| TOKEN_LOADED = 0, |
| // Token was revoked as part of Dice migration. |
| TOKEN_REVOKED_DICE_MIGRATION = 1, |
| // Token was revoked because it is a secondary account and account consistency |
| // is disabled. |
| TOKEN_REVOKED_SECONDARY_ACCOUNT = 2, |
| // Token was revoked on load due to cookie settings. |
| TOKEN_REVOKED_ON_LOAD = 3, |
| |
| NUM_LOAD_TOKEN_FROM_DB_STATUS |
| }; |
| |
| // Used to record events related to token revocation requests in histograms. |
| // Do not change existing values, new values can only be added at the end. |
| enum class TokenRevocationRequestProgress { |
| // The request was created. |
| kRequestCreated = 0, |
| // The request was sent over the network. |
| kRequestStarted = 1, |
| // The network request completed with a failure. |
| kRequestFailed = 2, |
| // The network request completed with a success. |
| kRequestSucceeded = 3, |
| |
| kMaxValue = kRequestSucceeded |
| }; |
| |
| // Adds a sample to the TokenStateTransition histogram. Encapsuled in a function |
| // to reduce executable size, because histogram macros may generate a lot of |
| // code. |
| void RecordTokenStateTransition(TokenStateTransition transition) { |
| UMA_HISTOGRAM_ENUMERATION("Signin.TokenStateTransition", transition, |
| TokenStateTransition::kCount); |
| } |
| |
| // Adds a sample to the TokenRevocationRequestProgress histogram. Encapsuled in |
| // a function to reduce executable size, because histogram macros may generate a |
| // lot of code. |
| void RecordRefreshTokenRevocationRequestEvent( |
| TokenRevocationRequestProgress event) { |
| UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationRequestProgress", |
| event); |
| } |
| |
| // Record metrics when a token was updated. |
| void RecordTokenChanged(const std::string& existing_token, |
| const std::string& new_token) { |
| DCHECK_NE(existing_token, new_token); |
| DCHECK(!new_token.empty()); |
| TokenStateTransition transition = TokenStateTransition::kCount; |
| if (existing_token.empty()) { |
| transition = (new_token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) |
| ? TokenStateTransition::kNoneToInvalid |
| : TokenStateTransition::kNoneToRegular; |
| } else if (existing_token == |
| OAuth2TokenServiceDelegate::kInvalidRefreshToken) { |
| transition = TokenStateTransition::kInvalidToRegular; |
| } else { |
| // Existing token is a regular token. |
| transition = (new_token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) |
| ? TokenStateTransition::kRegularToInvalid |
| : TokenStateTransition::kRegularToRegular; |
| } |
| DCHECK_NE(TokenStateTransition::kCount, transition); |
| RecordTokenStateTransition(transition); |
| } |
| |
| // Record metrics when a token was loaded. |
| void RecordTokenLoaded(const std::string& token) { |
| RecordTokenStateTransition( |
| (token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) |
| ? TokenStateTransition::kLoadInvalid |
| : TokenStateTransition::kLoadRegular); |
| } |
| |
| // Record metrics when a token was revoked. |
| void RecordTokenRevoked(const std::string& token) { |
| RecordTokenStateTransition( |
| (token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) |
| ? TokenStateTransition::kInvalidToNone |
| : TokenStateTransition::kRegularToNone); |
| } |
| |
| std::string ApplyAccountIdPrefix(const std::string& account_id) { |
| return kAccountIdPrefix + account_id; |
| } |
| |
| bool IsLegacyRefreshTokenId(const std::string& service_id) { |
| return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken; |
| } |
| |
| bool IsLegacyServiceId(const std::string& account_id) { |
| return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0; |
| } |
| |
| std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) { |
| return prefixed_account_id.substr(kAccountIdPrefixLength); |
| } |
| |
| OAuth2TokenServiceDelegate::LoadCredentialsState |
| LoadCredentialsStateFromTokenResult(TokenServiceTable::Result token_result) { |
| switch (token_result) { |
| case TokenServiceTable::TOKEN_DB_RESULT_SQL_INVALID_STATEMENT: |
| case TokenServiceTable::TOKEN_DB_RESULT_BAD_ENTRY: |
| return OAuth2TokenServiceDelegate:: |
| LOAD_CREDENTIALS_FINISHED_WITH_DB_ERRORS; |
| case TokenServiceTable::TOKEN_DB_RESULT_DECRYPT_ERROR: |
| return OAuth2TokenServiceDelegate:: |
| LOAD_CREDENTIALS_FINISHED_WITH_DECRYPT_ERRORS; |
| case TokenServiceTable::TOKEN_DB_RESULT_SUCCESS: |
| return OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS; |
| } |
| NOTREACHED(); |
| return OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_UNKNOWN; |
| } |
| |
| // Returns whether the token service should be migrated to Dice. |
| // Migration can happen if the following conditions are met: |
| // - Token service Dice migration is not already done, |
| // - AccountTrackerService migration is done, |
| // - All accounts in the AccountTrackerService are valid, |
| // - Account consistency is DiceMigration or greater. |
| // TODO(droger): Remove this code once Dice is fully enabled. |
| bool ShouldMigrateToDice(signin::AccountConsistencyMethod account_consistency, |
| PrefService* prefs, |
| AccountTrackerService* account_tracker, |
| const std::map<std::string, std::string>& db_tokens) { |
| AccountTrackerService::AccountIdMigrationState migration_state = |
| account_tracker->GetMigrationState(); |
| if ((account_consistency == signin::AccountConsistencyMethod::kMirror) || |
| !signin::DiceMethodGreaterOrEqual( |
| account_consistency, |
| signin::AccountConsistencyMethod::kDiceMigration) || |
| (migration_state != AccountTrackerService::MIGRATION_DONE) || |
| prefs->GetBoolean(prefs::kTokenServiceDiceCompatible)) { |
| return false; |
| } |
| |
| // Do not migrate if some accounts are not valid. |
| for (std::map<std::string, std::string>::const_iterator iter = |
| db_tokens.begin(); |
| iter != db_tokens.end(); ++iter) { |
| const std::string& prefixed_account_id = iter->first; |
| std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); |
| AccountInfo account_info = account_tracker->GetAccountInfo(account_id); |
| if (!account_info.IsValid()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| // This class sends a request to GAIA to revoke the given refresh token from |
| // the server. This is a best effort attempt only. This class deletes itself |
| // when done successfully or otherwise. |
| class MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken |
| : public GaiaAuthConsumer { |
| public: |
| RevokeServerRefreshToken( |
| MutableProfileOAuth2TokenServiceDelegate* token_service_delegate, |
| SigninClient* client, |
| const std::string& refresh_token); |
| ~RevokeServerRefreshToken() override; |
| |
| // Starts the network request. |
| static void Start(base::WeakPtr<RevokeServerRefreshToken> rsrt, |
| const std::string& refresh_token); |
| |
| private: |
| // GaiaAuthConsumer overrides: |
| void OnOAuth2RevokeTokenCompleted( |
| GaiaAuthConsumer::TokenRevocationStatus status) override; |
| |
| MutableProfileOAuth2TokenServiceDelegate* token_service_delegate_; |
| GaiaAuthFetcher fetcher_; |
| base::WeakPtrFactory<RevokeServerRefreshToken> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken); |
| }; |
| |
| MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: |
| RevokeServerRefreshToken( |
| MutableProfileOAuth2TokenServiceDelegate* token_service_delegate, |
| SigninClient* client, |
| const std::string& refresh_token) |
| : token_service_delegate_(token_service_delegate), |
| fetcher_(this, |
| GaiaConstants::kChromeSource, |
| token_service_delegate_->GetRequestContext()), |
| weak_ptr_factory_(this) { |
| RecordRefreshTokenRevocationRequestEvent( |
| TokenRevocationRequestProgress::kRequestCreated); |
| client->DelayNetworkCall( |
| base::Bind(&MutableProfileOAuth2TokenServiceDelegate:: |
| RevokeServerRefreshToken::Start, |
| weak_ptr_factory_.GetWeakPtr(), refresh_token)); |
| } |
| |
| // static |
| void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::Start( |
| base::WeakPtr<RevokeServerRefreshToken> rsrt, |
| const std::string& refresh_token) { |
| if (!rsrt) |
| return; |
| RecordRefreshTokenRevocationRequestEvent( |
| TokenRevocationRequestProgress::kRequestStarted); |
| rsrt->fetcher_.StartRevokeOAuth2Token(refresh_token); |
| } |
| |
| MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: |
| ~RevokeServerRefreshToken() { |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: |
| OnOAuth2RevokeTokenCompleted( |
| GaiaAuthConsumer::TokenRevocationStatus status) { |
| RecordRefreshTokenRevocationRequestEvent( |
| (status == GaiaAuthConsumer::TokenRevocationStatus::kSuccess) |
| ? TokenRevocationRequestProgress::kRequestSucceeded |
| : TokenRevocationRequestProgress::kRequestFailed); |
| UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationStatus", status); |
| // |this| pointer will be deleted when removed from the vector, so don't |
| // access any members after call to erase(). |
| token_service_delegate_->server_revokes_.erase(std::find_if( |
| token_service_delegate_->server_revokes_.begin(), |
| token_service_delegate_->server_revokes_.end(), |
| [this](const std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate:: |
| RevokeServerRefreshToken>& item) { |
| return item.get() == this; |
| })); |
| } |
| |
| MutableProfileOAuth2TokenServiceDelegate::AccountStatus::AccountStatus( |
| SigninErrorController* signin_error_controller, |
| const std::string& account_id, |
| const std::string& refresh_token) |
| : signin_error_controller_(signin_error_controller), |
| account_id_(account_id), |
| refresh_token_(refresh_token), |
| last_auth_error_(GoogleServiceAuthError::NONE) { |
| DCHECK(signin_error_controller_); |
| DCHECK(!account_id_.empty()); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::AccountStatus::Initialize() { |
| signin_error_controller_->AddProvider(this); |
| } |
| |
| MutableProfileOAuth2TokenServiceDelegate::AccountStatus::~AccountStatus() { |
| signin_error_controller_->RemoveProvider(this); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::AccountStatus::SetLastAuthError( |
| const GoogleServiceAuthError& error) { |
| last_auth_error_ = error; |
| signin_error_controller_->AuthStatusChanged(); |
| } |
| |
| std::string |
| MutableProfileOAuth2TokenServiceDelegate::AccountStatus::GetAccountId() const { |
| return account_id_; |
| } |
| |
| GoogleServiceAuthError |
| MutableProfileOAuth2TokenServiceDelegate::AccountStatus::GetAuthStatus() const { |
| return last_auth_error_; |
| } |
| |
| MutableProfileOAuth2TokenServiceDelegate:: |
| MutableProfileOAuth2TokenServiceDelegate( |
| SigninClient* client, |
| SigninErrorController* signin_error_controller, |
| AccountTrackerService* account_tracker_service, |
| signin::AccountConsistencyMethod account_consistency, |
| bool revoke_all_tokens_on_load) |
| : web_data_service_request_(0), |
| load_credentials_state_(LOAD_CREDENTIALS_NOT_STARTED), |
| backoff_entry_(&backoff_policy_), |
| backoff_error_(GoogleServiceAuthError::NONE), |
| client_(client), |
| signin_error_controller_(signin_error_controller), |
| account_tracker_service_(account_tracker_service), |
| account_consistency_(account_consistency), |
| revoke_all_tokens_on_load_(revoke_all_tokens_on_load) { |
| VLOG(1) << "MutablePO2TS::MutablePO2TS"; |
| DCHECK(client); |
| DCHECK(signin_error_controller); |
| DCHECK(account_tracker_service_); |
| // It's okay to fill the backoff policy after being used in construction. |
| backoff_policy_.num_errors_to_ignore = 0; |
| backoff_policy_.initial_delay_ms = 1000; |
| backoff_policy_.multiply_factor = 2.0; |
| backoff_policy_.jitter_factor = 0.2; |
| backoff_policy_.maximum_backoff_ms = 15 * 60 * 1000; |
| backoff_policy_.entry_lifetime_ms = -1; |
| backoff_policy_.always_use_initial_delay = false; |
| net::NetworkChangeNotifier::AddNetworkChangeObserver(this); |
| } |
| |
| MutableProfileOAuth2TokenServiceDelegate:: |
| ~MutableProfileOAuth2TokenServiceDelegate() { |
| VLOG(1) << "MutablePO2TS::~MutablePO2TS"; |
| DCHECK(server_revokes_.empty()); |
| net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); |
| } |
| |
| // static |
| void MutableProfileOAuth2TokenServiceDelegate::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref(prefs::kTokenServiceDiceCompatible, false); |
| } |
| |
| OAuth2AccessTokenFetcher* |
| MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher( |
| const std::string& account_id, |
| net::URLRequestContextGetter* getter, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| OAuth2AccessTokenConsumer* consumer) { |
| ValidateAccountId(account_id); |
| // check whether the account has persistent error. |
| if (refresh_tokens_[account_id]->GetAuthStatus().IsPersistentError()) { |
| VLOG(1) << "Request for token has been rejected due to persistent error #" |
| << refresh_tokens_[account_id]->GetAuthStatus().state(); |
| return new OAuth2AccessTokenFetcherImmediateError( |
| consumer, refresh_tokens_[account_id]->GetAuthStatus()); |
| } |
| if (backoff_entry_.ShouldRejectRequest()) { |
| VLOG(1) << "Request for token has been rejected due to backoff rules from" |
| << " previous error #" << backoff_error_.state(); |
| return new OAuth2AccessTokenFetcherImmediateError(consumer, backoff_error_); |
| } |
| std::string refresh_token = GetRefreshToken(account_id); |
| DCHECK(!refresh_token.empty()); |
| return new OAuth2AccessTokenFetcherImpl(consumer, url_loader_factory, |
| refresh_token); |
| } |
| |
| GoogleServiceAuthError MutableProfileOAuth2TokenServiceDelegate::GetAuthError( |
| const std::string& account_id) const { |
| auto it = refresh_tokens_.find(account_id); |
| return (it == refresh_tokens_.end()) ? GoogleServiceAuthError::AuthErrorNone() |
| : it->second->GetAuthStatus(); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::UpdateAuthError( |
| const std::string& account_id, |
| const GoogleServiceAuthError& error) { |
| VLOG(1) << "MutablePO2TS::UpdateAuthError. Error: " << error.state() |
| << " account_id=" << account_id; |
| backoff_entry_.InformOfRequest(!error.IsTransientError()); |
| ValidateAccountId(account_id); |
| |
| // Do not report connection errors as these are not actually auth errors. |
| // We also want to avoid masking a "real" auth error just because we |
| // subsequently get a transient network error. We do keep it around though |
| // to report for future requests being denied for "backoff" reasons. |
| if (error.IsTransientError()) { |
| backoff_error_ = error; |
| return; |
| } |
| |
| if (refresh_tokens_.count(account_id) == 0) { |
| // This could happen if the preferences have been corrupted (see |
| // http://crbug.com/321370). In a Debug build that would be a bug, but in a |
| // Release build we want to deal with it gracefully. |
| NOTREACHED(); |
| return; |
| } |
| |
| AccountStatus* status = refresh_tokens_[account_id].get(); |
| if (error != status->GetAuthStatus()) { |
| status->SetLastAuthError(error); |
| FireAuthErrorChanged(account_id, error); |
| } |
| } |
| |
| bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable( |
| const std::string& account_id) const { |
| VLOG(1) << "MutablePO2TS::RefreshTokenIsAvailable"; |
| return !GetRefreshToken(account_id).empty(); |
| } |
| |
| std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshToken( |
| const std::string& account_id) const { |
| AccountStatusMap::const_iterator iter = refresh_tokens_.find(account_id); |
| if (iter != refresh_tokens_.end()) |
| return iter->second->refresh_token(); |
| return std::string(); |
| } |
| |
| std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshTokenForTest( |
| const std::string& account_id) const { |
| return GetRefreshToken(account_id); |
| } |
| |
| std::vector<std::string> |
| MutableProfileOAuth2TokenServiceDelegate::GetAccounts() { |
| std::vector<std::string> account_ids; |
| for (auto& token : refresh_tokens_) { |
| account_ids.push_back(token.first); |
| } |
| return account_ids; |
| } |
| |
| net::URLRequestContextGetter* |
| MutableProfileOAuth2TokenServiceDelegate::GetRequestContext() const { |
| return client_->GetURLRequestContext(); |
| } |
| |
| scoped_refptr<network::SharedURLLoaderFactory> |
| MutableProfileOAuth2TokenServiceDelegate::GetURLLoaderFactory() const { |
| return client_->GetURLLoaderFactory(); |
| } |
| |
| OAuth2TokenServiceDelegate::LoadCredentialsState |
| MutableProfileOAuth2TokenServiceDelegate::GetLoadCredentialsState() const { |
| return load_credentials_state_; |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::LoadCredentials( |
| const std::string& primary_account_id) { |
| if (load_credentials_state_ == LOAD_CREDENTIALS_IN_PROGRESS) { |
| VLOG(1) << "Load credentials operation already in progress"; |
| return; |
| } |
| |
| load_credentials_state_ = LOAD_CREDENTIALS_IN_PROGRESS; |
| if (primary_account_id.empty() && |
| (account_consistency_ == |
| signin::AccountConsistencyMethod::kDiceFixAuthErrors || |
| account_consistency_ == signin::AccountConsistencyMethod::kDisabled)) { |
| load_credentials_state_ = LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS; |
| FireRefreshTokensLoaded(); |
| return; |
| } |
| |
| if (!primary_account_id.empty()) |
| ValidateAccountId(primary_account_id); |
| DCHECK(loading_primary_account_id_.empty()); |
| DCHECK_EQ(0, web_data_service_request_); |
| |
| refresh_tokens_.clear(); |
| |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| if (!token_web_data) { |
| // This case only exists in unit tests that do not care about loading |
| // credentials. |
| load_credentials_state_ = LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS; |
| FireRefreshTokensLoaded(); |
| return; |
| } |
| |
| if (!primary_account_id.empty()) { |
| // If the account_id is an email address, then canonicalize it. This |
| // is to support legacy account_ids, and will not be needed after |
| // switching to gaia-ids. |
| if (primary_account_id.find('@') != std::string::npos) { |
| loading_primary_account_id_ = gaia::CanonicalizeEmail(primary_account_id); |
| } else { |
| loading_primary_account_id_ = primary_account_id; |
| } |
| } |
| |
| web_data_service_request_ = token_web_data->GetAllTokens(this); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::OnWebDataServiceRequestDone( |
| WebDataServiceBase::Handle handle, |
| std::unique_ptr<WDTypedResult> result) { |
| VLOG(1) << "MutablePO2TS::OnWebDataServiceRequestDone. Result type: " |
| << (result.get() == nullptr ? -1 |
| : static_cast<int>(result->GetType())); |
| |
| DCHECK_EQ(web_data_service_request_, handle); |
| web_data_service_request_ = 0; |
| |
| if (result) { |
| DCHECK(result->GetType() == TOKEN_RESULT); |
| const WDResult<TokenResult>* token_result = |
| static_cast<const WDResult<TokenResult>*>(result.get()); |
| LoadAllCredentialsIntoMemory(token_result->GetValue().tokens); |
| load_credentials_state_ = |
| LoadCredentialsStateFromTokenResult(token_result->GetValue().db_result); |
| } else { |
| load_credentials_state_ = LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS; |
| } |
| |
| // Make sure that we have an entry for |loading_primary_account_id_| in the |
| // map. The entry could be missing if there is a corruption in the token DB |
| // while this profile is connected to an account. |
| DCHECK(!loading_primary_account_id_.empty() || |
| account_consistency_ == signin::AccountConsistencyMethod::kMirror || |
| signin::DiceMethodGreaterOrEqual( |
| account_consistency_, |
| signin::AccountConsistencyMethod::kDicePrepareMigration)); |
| if (!loading_primary_account_id_.empty() && |
| refresh_tokens_.count(loading_primary_account_id_) == 0) { |
| if (load_credentials_state_ == LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS) { |
| load_credentials_state_ = |
| LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT; |
| } |
| AddAccountStatus(loading_primary_account_id_, std::string(), |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_MISSING)); |
| } |
| |
| // If we don't have a refresh token for a known account, signal an error. |
| for (auto& token : refresh_tokens_) { |
| if (!RefreshTokenIsAvailable(token.first)) { |
| UpdateAuthError(token.first, |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_MISSING)); |
| break; |
| } |
| } |
| |
| loading_primary_account_id_.clear(); |
| FireRefreshTokensLoaded(); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::LoadAllCredentialsIntoMemory( |
| const std::map<std::string, std::string>& db_tokens) { |
| std::string old_login_token; |
| bool migrate_to_dice = |
| ShouldMigrateToDice(account_consistency_, client_->GetPrefs(), |
| account_tracker_service_, db_tokens); |
| |
| { |
| ScopedBatchChange batch(this); |
| |
| VLOG(1) << "MutablePO2TS::LoadAllCredentialsIntoMemory; " |
| << db_tokens.size() << " Credential(s)."; |
| AccountTrackerService::AccountIdMigrationState migration_state = |
| account_tracker_service_->GetMigrationState(); |
| for (std::map<std::string, std::string>::const_iterator iter = |
| db_tokens.begin(); |
| iter != db_tokens.end(); ++iter) { |
| std::string prefixed_account_id = iter->first; |
| std::string refresh_token = iter->second; |
| |
| if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty()) |
| old_login_token = refresh_token; |
| |
| if (IsLegacyServiceId(prefixed_account_id)) { |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| if (token_web_data.get()) { |
| VLOG(1) << "MutablePO2TS remove legacy refresh token for account id " |
| << prefixed_account_id; |
| token_web_data->RemoveTokenForService(prefixed_account_id); |
| } |
| } else { |
| DCHECK(!refresh_token.empty()); |
| std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); |
| |
| switch (migration_state) { |
| case AccountTrackerService::MIGRATION_IN_PROGRESS: { |
| // Migrate to gaia-ids. |
| AccountInfo account_info = |
| account_tracker_service_->FindAccountInfoByEmail(account_id); |
| // |account_info.gaia| could be empty if |account_id| is already |
| // gaia id. This could happen if the chrome was closed in the middle |
| // of migration. |
| if (!account_info.gaia.empty()) { |
| ClearPersistedCredentials(account_id); |
| PersistCredentials(account_info.gaia, refresh_token); |
| account_id = account_info.gaia; |
| } |
| |
| // Skip duplicate accounts, this could happen if migration was |
| // crashed in the middle. |
| if (refresh_tokens_.count(account_id) != 0) |
| continue; |
| break; |
| } |
| case AccountTrackerService::MIGRATION_NOT_STARTED: |
| // If the account_id is an email address, then canonicalize it. This |
| // is to support legacy account_ids, and will not be needed after |
| // switching to gaia-ids. |
| if (account_id.find('@') != std::string::npos) { |
| // If the canonical account id is not the same as the loaded |
| // account id, make sure not to overwrite a refresh token from |
| // a canonical version. If no canonical version was loaded, then |
| // re-persist this refresh token with the canonical account id. |
| std::string canon_account_id = |
| gaia::CanonicalizeEmail(account_id); |
| if (canon_account_id != account_id) { |
| ClearPersistedCredentials(account_id); |
| if (db_tokens.count(ApplyAccountIdPrefix(canon_account_id)) == |
| 0) |
| PersistCredentials(canon_account_id, refresh_token); |
| } |
| account_id = canon_account_id; |
| } |
| break; |
| case AccountTrackerService::MIGRATION_DONE: |
| DCHECK_EQ(std::string::npos, account_id.find('@')); |
| break; |
| case AccountTrackerService::NUM_MIGRATION_STATES: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Only load secondary accounts when account consistency is enabled. |
| bool load_account = |
| (account_id == loading_primary_account_id_) || |
| (account_consistency_ == |
| signin::AccountConsistencyMethod::kMirror) || |
| signin::DiceMethodGreaterOrEqual( |
| account_consistency_, |
| signin::AccountConsistencyMethod::kDicePrepareMigration); |
| LoadTokenFromDBStatus load_token_status = |
| load_account |
| ? LoadTokenFromDBStatus::TOKEN_LOADED |
| : LoadTokenFromDBStatus::TOKEN_REVOKED_SECONDARY_ACCOUNT; |
| |
| if (migrate_to_dice) { |
| // Revoke old hosted domain accounts as part of Dice migration. |
| AccountInfo account_info = |
| account_tracker_service_->GetAccountInfo(account_id); |
| DCHECK(account_info.IsValid()); |
| if (account_info.hosted_domain != |
| AccountTrackerService::kNoHostedDomainFound) { |
| load_account = false; |
| load_token_status = |
| LoadTokenFromDBStatus::TOKEN_REVOKED_DICE_MIGRATION; |
| } |
| } |
| |
| if (load_account && revoke_all_tokens_on_load_) { |
| if (account_id == loading_primary_account_id_) { |
| RevokeCredentialsOnServer(refresh_token); |
| refresh_token = kInvalidRefreshToken; |
| PersistCredentials(account_id, refresh_token); |
| } else { |
| load_account = false; |
| } |
| load_token_status = LoadTokenFromDBStatus::TOKEN_REVOKED_ON_LOAD; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Signin.LoadTokenFromDB", load_token_status, |
| LoadTokenFromDBStatus::NUM_LOAD_TOKEN_FROM_DB_STATUS); |
| |
| if (load_account) { |
| RecordTokenLoaded(refresh_token); |
| UpdateCredentialsInMemory(account_id, refresh_token); |
| FireRefreshTokenAvailable(account_id); |
| } else { |
| RecordTokenRevoked(refresh_token); |
| RevokeCredentialsOnServer(refresh_token); |
| ClearPersistedCredentials(account_id); |
| FireRefreshTokenRevoked(account_id); |
| } |
| } |
| } |
| |
| if (!old_login_token.empty()) { |
| DCHECK(!loading_primary_account_id_.empty()); |
| if (refresh_tokens_.count(loading_primary_account_id_) == 0) |
| UpdateCredentials(loading_primary_account_id_, old_login_token); |
| } |
| } |
| |
| if (migrate_to_dice) |
| client_->GetPrefs()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentials( |
| const std::string& account_id, |
| const std::string& refresh_token) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!account_id.empty()); |
| DCHECK(!refresh_token.empty()); |
| |
| ValidateAccountId(account_id); |
| signin_metrics::LogSigninAddAccount(); |
| |
| const std::string& existing_token = GetRefreshToken(account_id); |
| if (existing_token != refresh_token) { |
| ScopedBatchChange batch(this); |
| RecordTokenChanged(existing_token, refresh_token); |
| UpdateCredentialsInMemory(account_id, refresh_token); |
| PersistCredentials(account_id, refresh_token); |
| FireRefreshTokenAvailable(account_id); |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentialsInMemory( |
| const std::string& account_id, |
| const std::string& refresh_token) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!account_id.empty()); |
| DCHECK(!refresh_token.empty()); |
| |
| GoogleServiceAuthError error = |
| (refresh_token == kInvalidRefreshToken) |
| ? GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_CLIENT) |
| : GoogleServiceAuthError::AuthErrorNone(); |
| |
| bool refresh_token_present = refresh_tokens_.count(account_id) > 0; |
| // If token present, and different from the new one, cancel its requests, |
| // and clear the entries in cache related to that account. |
| if (refresh_token_present) { |
| DCHECK_NE(refresh_token, refresh_tokens_[account_id]->refresh_token()); |
| VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. " |
| << "account_id=" << account_id; |
| RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token()); |
| refresh_tokens_[account_id]->set_refresh_token(refresh_token); |
| UpdateAuthError(account_id, error); |
| } else { |
| VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. " |
| << "account_id=" << account_id; |
| AddAccountStatus(account_id, refresh_token, error); |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::PersistCredentials( |
| const std::string& account_id, |
| const std::string& refresh_token) { |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| if (token_web_data.get()) { |
| VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id; |
| token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id), |
| refresh_token); |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() { |
| if (!client_->CanRevokeCredentials()) |
| return; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| ScopedBatchChange batch(this); |
| |
| VLOG(1) << "MutablePO2TS::RevokeAllCredentials"; |
| CancelWebTokenFetch(); |
| |
| // Make a temporary copy of the account ids. |
| std::vector<std::string> accounts; |
| for (const auto& token : refresh_tokens_) |
| accounts.push_back(token.first); |
| for (const std::string& account : accounts) |
| RevokeCredentials(account); |
| |
| DCHECK_EQ(0u, refresh_tokens_.size()); |
| |
| // Make sure all tokens are removed. |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| if (token_web_data.get()) |
| token_web_data->RemoveAllTokens(); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials( |
| const std::string& account_id) { |
| ValidateAccountId(account_id); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (refresh_tokens_.count(account_id) > 0) { |
| VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id; |
| ScopedBatchChange batch(this); |
| const std::string& token = refresh_tokens_[account_id]->refresh_token(); |
| RecordTokenRevoked(token); |
| RevokeCredentialsOnServer(token); |
| refresh_tokens_.erase(account_id); |
| ClearPersistedCredentials(account_id); |
| FireRefreshTokenRevoked(account_id); |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials( |
| const std::string& account_id) { |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| if (token_web_data.get()) { |
| VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id=" |
| << account_id; |
| token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id)); |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer( |
| const std::string& refresh_token) { |
| if (refresh_token == kInvalidRefreshToken) |
| return; |
| |
| // Keep track or all server revoke requests. This way they can be deleted |
| // before the token service is shutdown and won't outlive the profile. |
| server_revokes_.push_back( |
| std::make_unique<RevokeServerRefreshToken>(this, client_, refresh_token)); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() { |
| if (web_data_service_request_ != 0) { |
| scoped_refptr<TokenWebData> token_web_data = client_->GetDatabase(); |
| DCHECK(token_web_data.get()); |
| token_web_data->CancelRequest(web_data_service_request_); |
| web_data_service_request_ = 0; |
| } |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::Shutdown() { |
| VLOG(1) << "MutablePO2TS::Shutdown"; |
| server_revokes_.clear(); |
| CancelWebTokenFetch(); |
| refresh_tokens_.clear(); |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::OnNetworkChanged( |
| net::NetworkChangeNotifier::ConnectionType type) { |
| // If our network has changed, reset the backoff timer so that errors caused |
| // by a previous lack of network connectivity don't prevent new requests. |
| backoff_entry_.Reset(); |
| } |
| |
| const net::BackoffEntry* |
| MutableProfileOAuth2TokenServiceDelegate::BackoffEntry() const { |
| return &backoff_entry_; |
| } |
| |
| void MutableProfileOAuth2TokenServiceDelegate::AddAccountStatus( |
| const std::string& account_id, |
| const std::string& refresh_token, |
| const GoogleServiceAuthError& error) { |
| DCHECK_EQ(0u, refresh_tokens_.count(account_id)); |
| AccountStatus* status = |
| new AccountStatus(signin_error_controller_, account_id, refresh_token); |
| refresh_tokens_[account_id].reset(status); |
| status->Initialize(); |
| status->SetLastAuthError(error); |
| FireAuthErrorChanged(account_id, status->GetAuthStatus()); |
| } |