blob: b191706de131c40cf191442088602a07bb1e1333 [file] [log] [blame]
// 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 "components/signin/core/browser/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 "build/build_config.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_info.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 "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,
kLoadInvalidNoTokenForPrimaryAccount,
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_FINISHED_WITH_UNKNOWN_ERRORS;
}
// 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 (auto 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,
int attempt);
~RevokeServerRefreshToken() override;
private:
// Starts the network request.
void Start();
// Returns true if the request should be retried.
bool ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status);
// GaiaAuthConsumer overrides:
void OnOAuth2RevokeTokenCompleted(
GaiaAuthConsumer::TokenRevocationStatus status) override;
MutableProfileOAuth2TokenServiceDelegate* token_service_delegate_;
GaiaAuthFetcher fetcher_;
std::string refresh_token_;
int attempt_;
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,
int attempt)
: token_service_delegate_(token_service_delegate),
fetcher_(this,
gaia::GaiaSource::kChrome,
token_service_delegate_->GetURLLoaderFactory()),
refresh_token_(refresh_token),
attempt_(attempt),
weak_ptr_factory_(this) {
RecordRefreshTokenRevocationRequestEvent(
TokenRevocationRequestProgress::kRequestCreated);
client->DelayNetworkCall(
base::BindRepeating(&MutableProfileOAuth2TokenServiceDelegate::
RevokeServerRefreshToken::Start,
weak_ptr_factory_.GetWeakPtr()));
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
Start() {
RecordRefreshTokenRevocationRequestEvent(
TokenRevocationRequestProgress::kRequestStarted);
fetcher_.StartRevokeOAuth2Token(refresh_token_);
}
MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
~RevokeServerRefreshToken() {}
bool MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status) {
// Token revocation can be retried up to 3 times.
if (attempt_ >= 2)
return false;
switch (status) {
case GaiaAuthConsumer::TokenRevocationStatus::kServerError:
case GaiaAuthConsumer::TokenRevocationStatus::kConnectionFailed:
case GaiaAuthConsumer::TokenRevocationStatus::kConnectionTimeout:
case GaiaAuthConsumer::TokenRevocationStatus::kConnectionCanceled:
return true;
case GaiaAuthConsumer::TokenRevocationStatus::kSuccess:
case GaiaAuthConsumer::TokenRevocationStatus::kInvalidToken:
case GaiaAuthConsumer::TokenRevocationStatus::kInvalidRequest:
case GaiaAuthConsumer::TokenRevocationStatus::kUnknownError:
return false;
}
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
OnOAuth2RevokeTokenCompleted(
GaiaAuthConsumer::TokenRevocationStatus status) {
UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationStatus", status);
if (ShouldRetry(status)) {
token_service_delegate_->server_revokes_.push_back(
std::make_unique<RevokeServerRefreshToken>(
token_service_delegate_, token_service_delegate_->client_,
refresh_token_, attempt_ + 1));
} else {
RecordRefreshTokenRevocationRequestEvent(
(status == GaiaAuthConsumer::TokenRevocationStatus::kSuccess)
? TokenRevocationRequestProgress::kRequestSucceeded
: TokenRevocationRequestProgress::kRequestFailed);
UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationCompleted", 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::
MutableProfileOAuth2TokenServiceDelegate(
SigninClient* client,
AccountTrackerService* account_tracker_service,
network::NetworkConnectionTracker* network_connection_tracker,
scoped_refptr<TokenWebData> token_web_data,
signin::AccountConsistencyMethod account_consistency,
bool revoke_all_tokens_on_load,
bool can_revoke_credentials,
FixRequestErrorCallback fix_request_error_callback)
: web_data_service_request_(0),
backoff_entry_(&backoff_policy_),
backoff_error_(GoogleServiceAuthError::NONE),
client_(client),
account_tracker_service_(account_tracker_service),
network_connection_tracker_(network_connection_tracker),
token_web_data_(token_web_data),
account_consistency_(account_consistency),
revoke_all_tokens_on_load_(revoke_all_tokens_on_load),
can_revoke_credentials_(can_revoke_credentials),
fix_request_error_callback_(fix_request_error_callback) {
VLOG(1) << "MutablePO2TS::MutablePO2TS";
DCHECK(client);
DCHECK(account_tracker_service_);
DCHECK(network_connection_tracker_);
// 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;
network_connection_tracker_->AddNetworkConnectionObserver(this);
}
MutableProfileOAuth2TokenServiceDelegate::
~MutableProfileOAuth2TokenServiceDelegate() {
VLOG(1) << "MutablePO2TS::~MutablePO2TS";
DCHECK(server_revokes_.empty());
network_connection_tracker_->RemoveNetworkConnectionObserver(this);
}
// static
void MutableProfileOAuth2TokenServiceDelegate::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(prefs::kTokenServiceDiceCompatible, false);
}
OAuth2AccessTokenFetcher*
MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
const std::string& account_id,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
OAuth2AccessTokenConsumer* consumer) {
ValidateAccountId(account_id);
// check whether the account has persistent error.
if (refresh_tokens_[account_id].last_auth_error.IsPersistentError()) {
VLOG(1) << "Request for token has been rejected due to persistent error #"
<< refresh_tokens_[account_id].last_auth_error.state();
return new OAuth2AccessTokenFetcherImmediateError(
consumer, refresh_tokens_[account_id].last_auth_error);
}
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.last_auth_error;
}
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];
if (error != status->last_auth_error) {
status->last_auth_error = error;
FireAuthErrorChanged(account_id, error);
}
}
std::string MutableProfileOAuth2TokenServiceDelegate::GetTokenForMultilogin(
const std::string& account_id) const {
auto iter = refresh_tokens_.find(account_id);
if (iter == refresh_tokens_.end() ||
iter->second.last_auth_error != GoogleServiceAuthError::AuthErrorNone()) {
return std::string();
}
const std::string& refresh_token = iter->second.refresh_token;
DCHECK(!refresh_token.empty());
return refresh_token;
}
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 {
auto iter = refresh_tokens_.find(account_id);
if (iter != refresh_tokens_.end()) {
const std::string refresh_token = iter->second.refresh_token;
DCHECK(!refresh_token.empty());
return 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;
}
scoped_refptr<network::SharedURLLoaderFactory>
MutableProfileOAuth2TokenServiceDelegate::GetURLLoaderFactory() const {
return client_->GetURLLoaderFactory();
}
void MutableProfileOAuth2TokenServiceDelegate::InvalidateTokenForMultilogin(
const std::string& failed_account) {
UpdateAuthError(
failed_account,
GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
}
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;
}
set_load_credentials_state(LOAD_CREDENTIALS_IN_PROGRESS);
#if defined(OS_CHROMEOS)
// ChromeOS OOBE loads credentials without a primary account and expects this
// to be a no-op. See htttp://crbug.com/891818
if (primary_account_id.empty()) {
set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS);
FinishLoadingCredentials();
return;
}
#endif
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();
if (!token_web_data_) {
// This case only exists in unit tests that do not care about loading
// credentials.
set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS);
FinishLoadingCredentials();
return;
}
// If |account_id| is an email address, then canonicalize it. This is needed
// 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);
set_load_credentials_state(LoadCredentialsStateFromTokenResult(
token_result->GetValue().db_result));
} else {
set_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.
if (!loading_primary_account_id_.empty() &&
refresh_tokens_.count(loading_primary_account_id_) == 0) {
if (load_credentials_state() == LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS) {
set_load_credentials_state(
LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT);
}
AddAccountStatus(loading_primary_account_id_, kInvalidRefreshToken,
GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
GoogleServiceAuthError::InvalidGaiaCredentialsReason::
CREDENTIALS_MISSING));
RecordTokenStateTransition(
TokenStateTransition::kLoadInvalidNoTokenForPrimaryAccount);
FireRefreshTokenAvailable(loading_primary_account_id_);
}
#ifndef NDEBUG
for (auto& token : refresh_tokens_) {
DCHECK(RefreshTokenIsAvailable(token.first))
<< "Missing token for " << token.first;
}
#endif
loading_primary_account_id_.clear();
FinishLoadingCredentials();
}
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 (auto 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)) {
if (token_web_data_) {
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::kDiceMigration);
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 != 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_CALLED_ON_VALID_THREAD(thread_checker_);
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_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!account_id.empty());
DCHECK(!refresh_token.empty());
bool is_refresh_token_invalidated = refresh_token == kInvalidRefreshToken;
GoogleServiceAuthError error =
is_refresh_token_invalidated
? 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;
// The old refresh token must be revoked on the server only when it is
// invalidated.
//
// The refresh token is updated to a new valid one in case of reauth.
// In the reauth case the old and the new refresh tokens have the same
// device ID. When revoking a refresh token on the server, Gaia revokes
// all the refresh tokens that have the same device ID.
// Therefore, the old refresh token must not be revoked on the server
// when it is updated to a new valid one (otherwise the new refresh token
// would also be invalidated server-side).
// See http://crbug.com/865189 for more information about this regression.
if (is_refresh_token_invalidated)
RevokeCredentialsOnServer(refresh_tokens_[account_id].refresh_token);
refresh_tokens_[account_id].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) {
DCHECK(!account_id.empty());
DCHECK(!refresh_token.empty());
if (token_web_data_) {
VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id;
token_web_data_->SetTokenForService(ApplyAccountIdPrefix(account_id),
refresh_token);
}
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
if (!can_revoke_credentials_)
return;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
VLOG(1) << "MutablePO2TS::RevokeAllCredentials";
ScopedBatchChange batch(this);
if (load_credentials_state() == LOAD_CREDENTIALS_IN_PROGRESS) {
VLOG(1) << "MutablePO2TS::RevokeAllCredentials before tokens are loaded.";
// If |RevokeAllCredentials| is called while credentials are being loaded,
// then the load must be cancelled and the load credentials state updated.
DCHECK_NE(0, web_data_service_request_);
CancelWebTokenFetch();
set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS);
FinishLoadingCredentials();
}
// 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 from storage.
if (token_web_data_)
token_web_data_->RemoveAllTokens();
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials(
const std::string& account_id) {
RevokeCredentialsImpl(account_id, /*revoke_on_server=*/true);
}
void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials(
const std::string& account_id) {
DCHECK(!account_id.empty());
if (token_web_data_) {
VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id="
<< account_id;
token_web_data_->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
}
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer(
const std::string& refresh_token) {
DCHECK(!refresh_token.empty());
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, 0));
}
void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() {
if (web_data_service_request_ != 0) {
DCHECK(token_web_data_);
token_web_data_->CancelRequest(web_data_service_request_);
web_data_service_request_ = 0;
}
}
void MutableProfileOAuth2TokenServiceDelegate::ExtractCredentials(
OAuth2TokenService* to_service,
const std::string& account_id) {
static_cast<ProfileOAuth2TokenService*>(to_service)
->UpdateCredentials(account_id, GetRefreshToken(account_id),
signin_metrics::SourceForRefreshTokenOperation::
kTokenService_ExtractCredentials);
RevokeCredentialsImpl(account_id, /*revoke_on_server=*/false);
}
void MutableProfileOAuth2TokenServiceDelegate::Shutdown() {
VLOG(1) << "MutablePO2TS::Shutdown";
server_revokes_.clear();
CancelWebTokenFetch();
refresh_tokens_.clear();
OAuth2TokenServiceDelegate::Shutdown();
}
void MutableProfileOAuth2TokenServiceDelegate::OnConnectionChanged(
network::mojom::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_;
}
bool MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorIfPossible() {
return !fix_request_error_callback_.is_null()
? fix_request_error_callback_.Run()
: false;
}
void MutableProfileOAuth2TokenServiceDelegate::AddAccountStatus(
const std::string& account_id,
const std::string& refresh_token,
const GoogleServiceAuthError& error) {
DCHECK_EQ(0u, refresh_tokens_.count(account_id));
refresh_tokens_[account_id] = AccountStatus{refresh_token, error};
FireAuthErrorChanged(account_id, error);
}
void MutableProfileOAuth2TokenServiceDelegate::FinishLoadingCredentials() {
FireRefreshTokensLoaded();
}
void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsImpl(
const std::string& account_id,
bool revoke_on_server) {
ValidateAccountId(account_id);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
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);
if (revoke_on_server)
RevokeCredentialsOnServer(token);
refresh_tokens_.erase(account_id);
ClearPersistedCredentials(account_id);
FireRefreshTokenRevoked(account_id);
}
}