1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h"
6 
7 #include <stddef.h>
8 
9 #include <map>
10 #include <string>
11 
12 #include "base/bind.h"
13 #include "base/macros.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "components/prefs/pref_registry_simple.h"
17 #include "components/prefs/pref_service.h"
18 #include "components/signin/public/base/signin_client.h"
19 #include "components/signin/public/base/signin_metrics.h"
20 #include "components/signin/public/base/signin_pref_names.h"
21 #include "components/signin/public/identity_manager/account_info.h"
22 #include "components/signin/public/webdata/token_web_data.h"
23 #include "components/webdata/common/web_data_service_base.h"
24 #include "google_apis/gaia/gaia_auth_fetcher.h"
25 #include "google_apis/gaia/gaia_auth_util.h"
26 #include "google_apis/gaia/gaia_constants.h"
27 #include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"
28 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
29 #include "services/network/public/cpp/shared_url_loader_factory.h"
30 
31 namespace {
32 
33 const char kAccountIdPrefix[] = "AccountId-";
34 const size_t kAccountIdPrefixLength = 10;
35 
36 // Enum for the Signin.LoadTokenFromDB histogram.
37 // Do not modify, or add or delete other than directly before
38 // NUM_LOAD_TOKEN_FROM_DB_STATUS.
39 enum class LoadTokenFromDBStatus {
40   // Token was loaded.
41   TOKEN_LOADED = 0,
42   // Token was revoked as part of Dice migration.
43   TOKEN_REVOKED_DICE_MIGRATION = 1,
44   // Token was revoked because it is a secondary account and account consistency
45   // is disabled.
46   TOKEN_REVOKED_SECONDARY_ACCOUNT = 2,
47   // Token was revoked on load due to cookie settings.
48   TOKEN_REVOKED_ON_LOAD = 3,
49 
50   NUM_LOAD_TOKEN_FROM_DB_STATUS
51 };
52 
53 // Used to record events related to token revocation requests in histograms.
54 // Do not change existing values, new values can only be added at the end.
55 enum class TokenRevocationRequestProgress {
56   // The request was created.
57   kRequestCreated = 0,
58   // The request was sent over the network.
59   kRequestStarted = 1,
60   // The network request completed with a failure.
61   kRequestFailed = 2,
62   // The network request completed with a success.
63   kRequestSucceeded = 3,
64 
65   kMaxValue = kRequestSucceeded
66 };
67 
68 // Adds a sample to the TokenRevocationRequestProgress histogram. Encapsuled in
69 // a function to reduce executable size, because histogram macros may generate a
70 // lot of code.
RecordRefreshTokenRevocationRequestEvent(TokenRevocationRequestProgress event)71 void RecordRefreshTokenRevocationRequestEvent(
72     TokenRevocationRequestProgress event) {
73   UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationRequestProgress",
74                             event);
75 }
76 
ApplyAccountIdPrefix(const std::string & account_id)77 std::string ApplyAccountIdPrefix(const std::string& account_id) {
78   return kAccountIdPrefix + account_id;
79 }
80 
IsLegacyRefreshTokenId(const std::string & service_id)81 bool IsLegacyRefreshTokenId(const std::string& service_id) {
82   return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
83 }
84 
IsLegacyServiceId(const std::string & account_id)85 bool IsLegacyServiceId(const std::string& account_id) {
86   return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
87 }
88 
RemoveAccountIdPrefix(const std::string & prefixed_account_id)89 CoreAccountId RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
90   return CoreAccountId::FromString(
91       prefixed_account_id.substr(kAccountIdPrefixLength));
92 }
93 
LoadCredentialsStateFromTokenResult(TokenServiceTable::Result token_result)94 signin::LoadCredentialsState LoadCredentialsStateFromTokenResult(
95     TokenServiceTable::Result token_result) {
96   switch (token_result) {
97     case TokenServiceTable::TOKEN_DB_RESULT_SQL_INVALID_STATEMENT:
98     case TokenServiceTable::TOKEN_DB_RESULT_BAD_ENTRY:
99       return signin::LoadCredentialsState::
100           LOAD_CREDENTIALS_FINISHED_WITH_DB_ERRORS;
101     case TokenServiceTable::TOKEN_DB_RESULT_DECRYPT_ERROR:
102       return signin::LoadCredentialsState::
103           LOAD_CREDENTIALS_FINISHED_WITH_DECRYPT_ERRORS;
104     case TokenServiceTable::TOKEN_DB_RESULT_SUCCESS:
105       return signin::LoadCredentialsState::
106           LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS;
107   }
108   NOTREACHED();
109   return signin::LoadCredentialsState::
110       LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS;
111 }
112 
113 // Returns whether the token service should be migrated to Dice.
114 // Migration can happen if the following conditions are met:
115 // - Token service Dice migration is not already done,
116 // - Account consistency is DiceMigration or greater.
117 // TODO(droger): Remove this code once Dice is fully enabled.
ShouldMigrateToDice(signin::AccountConsistencyMethod account_consistency,PrefService * prefs)118 bool ShouldMigrateToDice(signin::AccountConsistencyMethod account_consistency,
119                          PrefService* prefs) {
120   return account_consistency == signin::AccountConsistencyMethod::kDice &&
121          !prefs->GetBoolean(prefs::kTokenServiceDiceCompatible);
122 }
123 
124 }  // namespace
125 
126 // This class sends a request to GAIA to revoke the given refresh token from
127 // the server.  This is a best effort attempt only.  This class deletes itself
128 // when done successfully or otherwise.
129 class MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken
130     : public GaiaAuthConsumer {
131  public:
132   RevokeServerRefreshToken(
133       MutableProfileOAuth2TokenServiceDelegate* token_service_delegate,
134       SigninClient* client,
135       const std::string& refresh_token,
136       int attempt);
137   ~RevokeServerRefreshToken() override;
138 
139  private:
140   // Starts the network request.
141   void Start();
142   // Returns true if the request should be retried.
143   bool ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status);
144   // GaiaAuthConsumer overrides:
145   void OnOAuth2RevokeTokenCompleted(
146       GaiaAuthConsumer::TokenRevocationStatus status) override;
147 
148   MutableProfileOAuth2TokenServiceDelegate* token_service_delegate_;
149   GaiaAuthFetcher fetcher_;
150   std::string refresh_token_;
151   int attempt_;
152   base::WeakPtrFactory<RevokeServerRefreshToken> weak_ptr_factory_{this};
153 
154   DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken);
155 };
156 
157 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
RevokeServerRefreshToken(MutableProfileOAuth2TokenServiceDelegate * token_service_delegate,SigninClient * client,const std::string & refresh_token,int attempt)158     RevokeServerRefreshToken(
159         MutableProfileOAuth2TokenServiceDelegate* token_service_delegate,
160         SigninClient* client,
161         const std::string& refresh_token,
162         int attempt)
163     : token_service_delegate_(token_service_delegate),
164       fetcher_(this,
165                gaia::GaiaSource::kChrome,
166                token_service_delegate_->GetURLLoaderFactory()),
167       refresh_token_(refresh_token),
168       attempt_(attempt) {
169   RecordRefreshTokenRevocationRequestEvent(
170       TokenRevocationRequestProgress::kRequestCreated);
171   client->DelayNetworkCall(
172       base::BindRepeating(&MutableProfileOAuth2TokenServiceDelegate::
173                               RevokeServerRefreshToken::Start,
174                           weak_ptr_factory_.GetWeakPtr()));
175 }
176 
177 void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
Start()178     Start() {
179   RecordRefreshTokenRevocationRequestEvent(
180       TokenRevocationRequestProgress::kRequestStarted);
181   fetcher_.StartRevokeOAuth2Token(refresh_token_);
182 }
183 
184 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
~RevokeServerRefreshToken()185     ~RevokeServerRefreshToken() {}
186 
187 bool MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status)188     ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status) {
189   // Token revocation can be retried up to 3 times.
190   if (attempt_ >= 2)
191     return false;
192 
193   switch (status) {
194     case GaiaAuthConsumer::TokenRevocationStatus::kServerError:
195     case GaiaAuthConsumer::TokenRevocationStatus::kConnectionFailed:
196     case GaiaAuthConsumer::TokenRevocationStatus::kConnectionTimeout:
197     case GaiaAuthConsumer::TokenRevocationStatus::kConnectionCanceled:
198       return true;
199     case GaiaAuthConsumer::TokenRevocationStatus::kSuccess:
200     case GaiaAuthConsumer::TokenRevocationStatus::kInvalidToken:
201     case GaiaAuthConsumer::TokenRevocationStatus::kInvalidRequest:
202     case GaiaAuthConsumer::TokenRevocationStatus::kUnknownError:
203       return false;
204   }
205 }
206 
207 void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
OnOAuth2RevokeTokenCompleted(GaiaAuthConsumer::TokenRevocationStatus status)208     OnOAuth2RevokeTokenCompleted(
209         GaiaAuthConsumer::TokenRevocationStatus status) {
210   UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationStatus", status);
211   if (ShouldRetry(status)) {
212     token_service_delegate_->server_revokes_.push_back(
213         std::make_unique<RevokeServerRefreshToken>(
214             token_service_delegate_, token_service_delegate_->client_,
215             refresh_token_, attempt_ + 1));
216   } else {
217     RecordRefreshTokenRevocationRequestEvent(
218         (status == GaiaAuthConsumer::TokenRevocationStatus::kSuccess)
219             ? TokenRevocationRequestProgress::kRequestSucceeded
220             : TokenRevocationRequestProgress::kRequestFailed);
221     UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationCompleted", status);
222   }
223   // |this| pointer will be deleted when removed from the vector, so don't
224   // access any members after call to erase().
225   token_service_delegate_->server_revokes_.erase(std::find_if(
226       token_service_delegate_->server_revokes_.begin(),
227       token_service_delegate_->server_revokes_.end(),
228       [this](const std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate::
229                                        RevokeServerRefreshToken>& item) {
230         return item.get() == this;
231       }));
232 }
233 
234 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,FixRequestErrorCallback fix_request_error_callback)235     MutableProfileOAuth2TokenServiceDelegate(
236         SigninClient* client,
237         AccountTrackerService* account_tracker_service,
238         network::NetworkConnectionTracker* network_connection_tracker,
239         scoped_refptr<TokenWebData> token_web_data,
240         signin::AccountConsistencyMethod account_consistency,
241         bool revoke_all_tokens_on_load,
242         FixRequestErrorCallback fix_request_error_callback)
243     : web_data_service_request_(0),
244       backoff_entry_(&backoff_policy_),
245       backoff_error_(GoogleServiceAuthError::NONE),
246       client_(client),
247       account_tracker_service_(account_tracker_service),
248       network_connection_tracker_(network_connection_tracker),
249       token_web_data_(token_web_data),
250       account_consistency_(account_consistency),
251       revoke_all_tokens_on_load_(revoke_all_tokens_on_load),
252       fix_request_error_callback_(fix_request_error_callback) {
253   VLOG(1) << "MutablePO2TS::MutablePO2TS";
254   DCHECK(client);
255   DCHECK(account_tracker_service_);
256   DCHECK(network_connection_tracker_);
257   DCHECK_NE(signin::AccountConsistencyMethod::kMirror, account_consistency_);
258   // It's okay to fill the backoff policy after being used in construction.
259   backoff_policy_.num_errors_to_ignore = 0;
260   backoff_policy_.initial_delay_ms = 1000;
261   backoff_policy_.multiply_factor = 2.0;
262   backoff_policy_.jitter_factor = 0.2;
263   backoff_policy_.maximum_backoff_ms = 15 * 60 * 1000;
264   backoff_policy_.entry_lifetime_ms = -1;
265   backoff_policy_.always_use_initial_delay = false;
266   network_connection_tracker_->AddNetworkConnectionObserver(this);
267 }
268 
269 MutableProfileOAuth2TokenServiceDelegate::
~MutableProfileOAuth2TokenServiceDelegate()270     ~MutableProfileOAuth2TokenServiceDelegate() {
271   VLOG(1) << "MutablePO2TS::~MutablePO2TS";
272   DCHECK(server_revokes_.empty());
273   network_connection_tracker_->RemoveNetworkConnectionObserver(this);
274 }
275 
276 // static
RegisterProfilePrefs(PrefRegistrySimple * registry)277 void MutableProfileOAuth2TokenServiceDelegate::RegisterProfilePrefs(
278     PrefRegistrySimple* registry) {
279   registry->RegisterBooleanPref(prefs::kTokenServiceDiceCompatible, false);
280 }
281 
282 std::unique_ptr<OAuth2AccessTokenFetcher>
CreateAccessTokenFetcher(const CoreAccountId & account_id,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,OAuth2AccessTokenConsumer * consumer)283 MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
284     const CoreAccountId& account_id,
285     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
286     OAuth2AccessTokenConsumer* consumer) {
287   ValidateAccountId(account_id);
288   // check whether the account has persistent error.
289   if (refresh_tokens_[account_id].last_auth_error.IsPersistentError()) {
290     VLOG(1) << "Request for token has been rejected due to persistent error #"
291             << refresh_tokens_[account_id].last_auth_error.state();
292     return std::make_unique<OAuth2AccessTokenFetcherImmediateError>(
293         consumer, refresh_tokens_[account_id].last_auth_error);
294   }
295   if (backoff_entry_.ShouldRejectRequest()) {
296     VLOG(1) << "Request for token has been rejected due to backoff rules from"
297             << " previous error #" << backoff_error_.state();
298     return std::make_unique<OAuth2AccessTokenFetcherImmediateError>(
299         consumer, backoff_error_);
300   }
301   std::string refresh_token = GetRefreshToken(account_id);
302   DCHECK(!refresh_token.empty());
303   return std::make_unique<OAuth2AccessTokenFetcherImpl>(
304       consumer, url_loader_factory, refresh_token);
305 }
306 
GetAuthError(const CoreAccountId & account_id) const307 GoogleServiceAuthError MutableProfileOAuth2TokenServiceDelegate::GetAuthError(
308     const CoreAccountId& account_id) const {
309   auto it = refresh_tokens_.find(account_id);
310   return (it == refresh_tokens_.end()) ? GoogleServiceAuthError::AuthErrorNone()
311                                        : it->second.last_auth_error;
312 }
313 
UpdateAuthError(const CoreAccountId & account_id,const GoogleServiceAuthError & error)314 void MutableProfileOAuth2TokenServiceDelegate::UpdateAuthError(
315     const CoreAccountId& account_id,
316     const GoogleServiceAuthError& error) {
317   VLOG(1) << "MutablePO2TS::UpdateAuthError. Error: " << error.state()
318           << " account_id=" << account_id;
319   backoff_entry_.InformOfRequest(!error.IsTransientError());
320   ValidateAccountId(account_id);
321 
322   // Do not report connection errors as these are not actually auth errors.
323   // We also want to avoid masking a "real" auth error just because we
324   // subsequently get a transient network error.  We do keep it around though
325   // to report for future requests being denied for "backoff" reasons.
326   if (error.IsTransientError()) {
327     backoff_error_ = error;
328     return;
329   }
330 
331   if (refresh_tokens_.count(account_id) == 0) {
332     // This could happen if the preferences have been corrupted (see
333     // http://crbug.com/321370). In a Debug build that would be a bug, but in a
334     // Release build we want to deal with it gracefully.
335     NOTREACHED();
336     return;
337   }
338 
339   AccountStatus* status = &refresh_tokens_[account_id];
340   if (error != status->last_auth_error) {
341     status->last_auth_error = error;
342     FireAuthErrorChanged(account_id, error);
343   }
344 }
345 
GetTokenForMultilogin(const CoreAccountId & account_id) const346 std::string MutableProfileOAuth2TokenServiceDelegate::GetTokenForMultilogin(
347     const CoreAccountId& account_id) const {
348   auto iter = refresh_tokens_.find(account_id);
349   if (iter == refresh_tokens_.end() ||
350       iter->second.last_auth_error != GoogleServiceAuthError::AuthErrorNone()) {
351     return std::string();
352   }
353   const std::string& refresh_token = iter->second.refresh_token;
354   DCHECK(!refresh_token.empty());
355   return refresh_token;
356 }
357 
RefreshTokenIsAvailable(const CoreAccountId & account_id) const358 bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
359     const CoreAccountId& account_id) const {
360   VLOG(1) << "MutablePO2TS::RefreshTokenIsAvailable";
361   return !GetRefreshToken(account_id).empty();
362 }
363 
GetRefreshToken(const CoreAccountId & account_id) const364 std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshToken(
365     const CoreAccountId& account_id) const {
366   auto iter = refresh_tokens_.find(account_id);
367   if (iter != refresh_tokens_.end()) {
368     const std::string refresh_token = iter->second.refresh_token;
369     DCHECK(!refresh_token.empty());
370     return refresh_token;
371   }
372   return std::string();
373 }
374 
GetRefreshTokenForTest(const CoreAccountId & account_id) const375 std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshTokenForTest(
376     const CoreAccountId& account_id) const {
377   return GetRefreshToken(account_id);
378 }
379 
380 std::vector<CoreAccountId>
GetAccounts() const381 MutableProfileOAuth2TokenServiceDelegate::GetAccounts() const {
382   std::vector<CoreAccountId> account_ids;
383   for (auto& token : refresh_tokens_) {
384     account_ids.push_back(token.first);
385   }
386   return account_ids;
387 }
388 
389 scoped_refptr<network::SharedURLLoaderFactory>
GetURLLoaderFactory() const390 MutableProfileOAuth2TokenServiceDelegate::GetURLLoaderFactory() const {
391   return client_->GetURLLoaderFactory();
392 }
393 
InvalidateTokenForMultilogin(const CoreAccountId & failed_account)394 void MutableProfileOAuth2TokenServiceDelegate::InvalidateTokenForMultilogin(
395     const CoreAccountId& failed_account) {
396   UpdateAuthError(
397       failed_account,
398       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
399 }
400 
LoadCredentials(const CoreAccountId & primary_account_id)401 void MutableProfileOAuth2TokenServiceDelegate::LoadCredentials(
402     const CoreAccountId& primary_account_id) {
403   if (load_credentials_state() ==
404       signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS) {
405     VLOG(1) << "Load credentials operation already in progress";
406     return;
407   }
408 
409   set_load_credentials_state(
410       signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS);
411 
412   if (!primary_account_id.empty())
413     ValidateAccountId(primary_account_id);
414   DCHECK(loading_primary_account_id_.empty());
415   DCHECK_EQ(0, web_data_service_request_);
416 
417   refresh_tokens_.clear();
418 
419   if (!token_web_data_) {
420     // This case only exists in unit tests that do not care about loading
421     // credentials.
422     set_load_credentials_state(
423         signin::LoadCredentialsState::
424             LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS);
425     MaybeDeletePreDiceTokens();
426     FinishLoadingCredentials();
427     return;
428   }
429 
430   // If |account_id| is an email address, then canonicalize it. This is needed
431   // to support legacy account IDs, and will not be needed after switching to
432   // gaia IDs.
433   if (primary_account_id.ToString().find('@') != std::string::npos) {
434     loading_primary_account_id_ = CoreAccountId::FromEmail(
435         gaia::CanonicalizeEmail(primary_account_id.ToString()));
436   } else {
437     loading_primary_account_id_ = primary_account_id;
438   }
439 
440   web_data_service_request_ = token_web_data_->GetAllTokens(this);
441 }
442 
OnWebDataServiceRequestDone(WebDataServiceBase::Handle handle,std::unique_ptr<WDTypedResult> result)443 void MutableProfileOAuth2TokenServiceDelegate::OnWebDataServiceRequestDone(
444     WebDataServiceBase::Handle handle,
445     std::unique_ptr<WDTypedResult> result) {
446   VLOG(1) << "MutablePO2TS::OnWebDataServiceRequestDone. Result type: "
447           << (result.get() == nullptr ? -1
448                                       : static_cast<int>(result->GetType()));
449 
450   DCHECK_EQ(web_data_service_request_, handle);
451   web_data_service_request_ = 0;
452 
453   if (result) {
454     DCHECK(result->GetType() == TOKEN_RESULT);
455     const WDResult<TokenResult>* token_result =
456         static_cast<const WDResult<TokenResult>*>(result.get());
457     LoadAllCredentialsIntoMemory(token_result->GetValue().tokens);
458     set_load_credentials_state(LoadCredentialsStateFromTokenResult(
459         token_result->GetValue().db_result));
460   } else {
461     set_load_credentials_state(
462         signin::LoadCredentialsState::
463             LOAD_CREDENTIALS_FINISHED_WITH_DB_CANNOT_BE_OPENED);
464     MaybeDeletePreDiceTokens();
465   }
466 
467   // Make sure that we have an entry for |loading_primary_account_id_| in the
468   // map.  The entry could be missing if there is a corruption in the token DB
469   // while this profile is connected to an account.
470   if (!loading_primary_account_id_.empty() &&
471       refresh_tokens_.count(loading_primary_account_id_) == 0) {
472     if (load_credentials_state() ==
473         signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS) {
474       set_load_credentials_state(
475           signin::LoadCredentialsState::
476               LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT);
477     }
478     AddAccountStatus(loading_primary_account_id_,
479                      GaiaConstants::kInvalidRefreshToken,
480                      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
481                          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
482                              CREDENTIALS_MISSING));
483     FireRefreshTokenAvailable(loading_primary_account_id_);
484   }
485 
486 #ifndef NDEBUG
487   for (auto& token : refresh_tokens_) {
488     DCHECK(RefreshTokenIsAvailable(token.first))
489         << "Missing token for " << token.first;
490   }
491 #endif
492 
493   loading_primary_account_id_ = CoreAccountId();
494   FinishLoadingCredentials();
495 }
496 
LoadAllCredentialsIntoMemory(const std::map<std::string,std::string> & db_tokens)497 void MutableProfileOAuth2TokenServiceDelegate::LoadAllCredentialsIntoMemory(
498     const std::map<std::string, std::string>& db_tokens) {
499   std::string old_login_token;
500   bool migrate_to_dice =
501       ShouldMigrateToDice(account_consistency_, client_->GetPrefs());
502 
503   {
504     ScopedBatchChange batch(this);
505 
506     VLOG(1) << "MutablePO2TS::LoadAllCredentialsIntoMemory; "
507             << db_tokens.size() << " Credential(s).";
508     AccountTrackerService::AccountIdMigrationState migration_state =
509         account_tracker_service_->GetMigrationState();
510     for (auto iter = db_tokens.begin(); iter != db_tokens.end(); ++iter) {
511       std::string prefixed_account_id = iter->first;
512       std::string refresh_token = iter->second;
513 
514       if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
515         old_login_token = refresh_token;
516 
517       if (IsLegacyServiceId(prefixed_account_id)) {
518         if (token_web_data_) {
519           VLOG(1) << "MutablePO2TS remove legacy refresh token for account id "
520                   << prefixed_account_id;
521           token_web_data_->RemoveTokenForService(prefixed_account_id);
522         }
523       } else {
524         DCHECK(!refresh_token.empty());
525         CoreAccountId account_id = RemoveAccountIdPrefix(prefixed_account_id);
526 
527         switch (migration_state) {
528           case AccountTrackerService::MIGRATION_IN_PROGRESS: {
529             // Migrate to gaia-ids.
530             AccountInfo account_info =
531                 account_tracker_service_->FindAccountInfoByEmail(
532                     account_id.ToString());
533             // |account_info| can be empty if |account_id| was already migrated.
534             // This could happen if the chrome was closed in the middle of the
535             // account id migration.
536             if (!account_info.IsEmpty()) {
537               ClearPersistedCredentials(account_id);
538               account_id = account_info.account_id;
539               PersistCredentials(account_id, refresh_token);
540             }
541 
542             // Skip duplicate accounts, this could happen if migration was
543             // crashed in the middle.
544             if (refresh_tokens_.count(account_id) != 0)
545               continue;
546             break;
547           }
548           case AccountTrackerService::MIGRATION_NOT_STARTED:
549             // If the account_id is an email address, then canonicalize it. This
550             // is to support legacy account_ids, and will not be needed after
551             // switching to gaia-ids.
552             if (account_id.ToString().find('@') != std::string::npos) {
553               // If the canonical account id is not the same as the loaded
554               // account id, make sure not to overwrite a refresh token from
555               // a canonical version.  If no canonical version was loaded, then
556               // re-persist this refresh token with the canonical account id.
557               CoreAccountId canon_account_id = CoreAccountId::FromEmail(
558                   gaia::CanonicalizeEmail(account_id.ToString()));
559               if (canon_account_id != account_id) {
560                 ClearPersistedCredentials(account_id);
561                 if (db_tokens.count(
562                         ApplyAccountIdPrefix(canon_account_id.ToString())) == 0)
563                   PersistCredentials(canon_account_id, refresh_token);
564               }
565               account_id = canon_account_id;
566             }
567             break;
568           case AccountTrackerService::MIGRATION_DONE:
569             DCHECK_EQ(std::string::npos, account_id.ToString().find('@'));
570             break;
571           case AccountTrackerService::NUM_MIGRATION_STATES:
572             NOTREACHED();
573             break;
574         }
575 
576         // Only load secondary accounts when account consistency is enabled.
577         bool load_account =
578             account_id == loading_primary_account_id_ ||
579             account_consistency_ == signin::AccountConsistencyMethod::kDice;
580         LoadTokenFromDBStatus load_token_status =
581             load_account
582                 ? LoadTokenFromDBStatus::TOKEN_LOADED
583                 : LoadTokenFromDBStatus::TOKEN_REVOKED_SECONDARY_ACCOUNT;
584 
585         if (migrate_to_dice) {
586           // Revoke old hosted domain accounts as part of Dice migration.
587           AccountInfo account_info =
588               account_tracker_service_->GetAccountInfo(account_id);
589           bool is_hosted_domain = false;
590           if (account_info.hosted_domain.empty()) {
591             // The AccountInfo is incomplete. Use a conservative approximation.
592             is_hosted_domain =
593                 !client_->IsNonEnterpriseUser(account_info.email);
594           } else {
595             is_hosted_domain =
596                 (account_info.hosted_domain != kNoHostedDomainFound);
597           }
598           if (is_hosted_domain) {
599             load_account = false;
600             load_token_status =
601                 LoadTokenFromDBStatus::TOKEN_REVOKED_DICE_MIGRATION;
602           }
603         }
604 
605         if (load_account && revoke_all_tokens_on_load_) {
606           if (account_id == loading_primary_account_id_) {
607             RevokeCredentialsOnServer(refresh_token);
608             refresh_token = GaiaConstants::kInvalidRefreshToken;
609             PersistCredentials(account_id, refresh_token);
610           } else {
611             load_account = false;
612           }
613           load_token_status = LoadTokenFromDBStatus::TOKEN_REVOKED_ON_LOAD;
614         }
615 
616         UMA_HISTOGRAM_ENUMERATION(
617             "Signin.LoadTokenFromDB", load_token_status,
618             LoadTokenFromDBStatus::NUM_LOAD_TOKEN_FROM_DB_STATUS);
619 
620         if (load_account) {
621           UpdateCredentialsInMemory(account_id, refresh_token);
622           FireRefreshTokenAvailable(account_id);
623         } else {
624           RevokeCredentialsOnServer(refresh_token);
625           ClearPersistedCredentials(account_id);
626           FireRefreshTokenRevoked(account_id);
627         }
628       }
629     }
630 
631     if (!old_login_token.empty()) {
632       DCHECK(!loading_primary_account_id_.empty());
633       if (refresh_tokens_.count(loading_primary_account_id_) == 0)
634         UpdateCredentials(loading_primary_account_id_, old_login_token);
635     }
636   }
637 
638   if (migrate_to_dice)
639     client_->GetPrefs()->SetBoolean(prefs::kTokenServiceDiceCompatible, true);
640 }
641 
UpdateCredentials(const CoreAccountId & account_id,const std::string & refresh_token)642 void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentials(
643     const CoreAccountId& account_id,
644     const std::string& refresh_token) {
645   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
646   DCHECK(!account_id.empty());
647   DCHECK(!refresh_token.empty());
648 
649   ValidateAccountId(account_id);
650   const std::string& existing_token = GetRefreshToken(account_id);
651   if (existing_token != refresh_token) {
652     ScopedBatchChange batch(this);
653     UpdateCredentialsInMemory(account_id, refresh_token);
654     PersistCredentials(account_id, refresh_token);
655     FireRefreshTokenAvailable(account_id);
656   }
657 }
658 
UpdateCredentialsInMemory(const CoreAccountId & account_id,const std::string & refresh_token)659 void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentialsInMemory(
660     const CoreAccountId& account_id,
661     const std::string& refresh_token) {
662   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
663   DCHECK(!account_id.empty());
664   DCHECK(!refresh_token.empty());
665 
666   bool is_refresh_token_invalidated =
667       refresh_token == GaiaConstants::kInvalidRefreshToken;
668   GoogleServiceAuthError error =
669       is_refresh_token_invalidated
670           ? GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
671                 GoogleServiceAuthError::InvalidGaiaCredentialsReason::
672                     CREDENTIALS_REJECTED_BY_CLIENT)
673           : GoogleServiceAuthError::AuthErrorNone();
674 
675   bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
676   // If token present, and different from the new one, cancel its requests,
677   // and clear the entries in cache related to that account.
678   if (refresh_token_present) {
679     DCHECK_NE(refresh_token, refresh_tokens_[account_id].refresh_token);
680     VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. "
681             << "account_id=" << account_id;
682 
683     // The old refresh token must be revoked on the server only when it is
684     // invalidated.
685     //
686     // The refresh token is updated to a new valid one in case of reauth.
687     // In the reauth case the old and the new refresh tokens have the same
688     // device ID. When revoking a refresh token on the server, Gaia revokes
689     // all the refresh tokens that have the same device ID.
690     // Therefore, the old refresh token must not be revoked on the server
691     // when it is updated to a new valid one (otherwise the new refresh token
692     // would also be invalidated server-side).
693     // See http://crbug.com/865189 for more information about this regression.
694     if (is_refresh_token_invalidated)
695       RevokeCredentialsOnServer(refresh_tokens_[account_id].refresh_token);
696 
697     refresh_tokens_[account_id].refresh_token = refresh_token;
698     UpdateAuthError(account_id, error);
699   } else {
700     VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. "
701             << "account_id=" << account_id;
702     AddAccountStatus(account_id, refresh_token, error);
703   }
704 }
705 
PersistCredentials(const CoreAccountId & account_id,const std::string & refresh_token)706 void MutableProfileOAuth2TokenServiceDelegate::PersistCredentials(
707     const CoreAccountId& account_id,
708     const std::string& refresh_token) {
709   DCHECK(!account_id.empty());
710   DCHECK(!refresh_token.empty());
711   if (token_web_data_) {
712     VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id;
713     token_web_data_->SetTokenForService(
714         ApplyAccountIdPrefix(account_id.ToString()), refresh_token);
715   }
716 }
717 
RevokeAllCredentials()718 void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
719   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
720 
721   VLOG(1) << "MutablePO2TS::RevokeAllCredentials";
722 
723   ScopedBatchChange batch(this);
724   if (load_credentials_state() ==
725       signin::LoadCredentialsState::LOAD_CREDENTIALS_IN_PROGRESS) {
726     VLOG(1) << "MutablePO2TS::RevokeAllCredentials before tokens are loaded.";
727     // If |RevokeAllCredentials| is called while credentials are being loaded,
728     // then the load must be cancelled and the load credentials state updated.
729     DCHECK_NE(0, web_data_service_request_);
730     CancelWebTokenFetch();
731     set_load_credentials_state(
732         signin::LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS);
733     FinishLoadingCredentials();
734   }
735 
736   // Make a temporary copy of the account ids.
737   std::vector<CoreAccountId> accounts;
738   for (const auto& token : refresh_tokens_)
739     accounts.push_back(token.first);
740   for (const auto& account : accounts)
741     RevokeCredentials(account);
742 
743   DCHECK_EQ(0u, refresh_tokens_.size());
744 
745   // Make sure all tokens are removed from storage.
746   if (token_web_data_)
747     token_web_data_->RemoveAllTokens();
748 }
749 
RevokeCredentials(const CoreAccountId & account_id)750 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials(
751     const CoreAccountId& account_id) {
752   RevokeCredentialsImpl(account_id, /*revoke_on_server=*/true);
753 }
754 
ClearPersistedCredentials(const CoreAccountId & account_id)755 void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials(
756     const CoreAccountId& account_id) {
757   DCHECK(!account_id.empty());
758   if (token_web_data_) {
759     VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id="
760             << account_id;
761     token_web_data_->RemoveTokenForService(
762         ApplyAccountIdPrefix(account_id.ToString()));
763   }
764 }
765 
RevokeCredentialsOnServer(const std::string & refresh_token)766 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer(
767     const std::string& refresh_token) {
768   DCHECK(!refresh_token.empty());
769 
770   if (refresh_token == GaiaConstants::kInvalidRefreshToken)
771     return;
772 
773   // Keep track or all server revoke requests.  This way they can be deleted
774   // before the token service is shutdown and won't outlive the profile.
775   server_revokes_.push_back(std::make_unique<RevokeServerRefreshToken>(
776       this, client_, refresh_token, 0));
777 }
778 
CancelWebTokenFetch()779 void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() {
780   if (web_data_service_request_ != 0) {
781     DCHECK(token_web_data_);
782     token_web_data_->CancelRequest(web_data_service_request_);
783     web_data_service_request_ = 0;
784   }
785 }
786 
ExtractCredentials(ProfileOAuth2TokenService * to_service,const CoreAccountId & account_id)787 void MutableProfileOAuth2TokenServiceDelegate::ExtractCredentials(
788     ProfileOAuth2TokenService* to_service,
789     const CoreAccountId& account_id) {
790   static_cast<ProfileOAuth2TokenService*>(to_service)
791       ->UpdateCredentials(account_id, GetRefreshToken(account_id),
792                           signin_metrics::SourceForRefreshTokenOperation::
793                               kTokenService_ExtractCredentials);
794   RevokeCredentialsImpl(account_id, /*revoke_on_server=*/false);
795 }
796 
Shutdown()797 void MutableProfileOAuth2TokenServiceDelegate::Shutdown() {
798   VLOG(1) << "MutablePO2TS::Shutdown";
799   server_revokes_.clear();
800   CancelWebTokenFetch();
801   refresh_tokens_.clear();
802   ProfileOAuth2TokenServiceDelegate::Shutdown();
803 }
804 
OnConnectionChanged(network::mojom::ConnectionType type)805 void MutableProfileOAuth2TokenServiceDelegate::OnConnectionChanged(
806     network::mojom::ConnectionType type) {
807   // If our network has changed, reset the backoff timer so that errors caused
808   // by a previous lack of network connectivity don't prevent new requests.
809   backoff_entry_.Reset();
810 }
811 
812 const net::BackoffEntry*
BackoffEntry() const813 MutableProfileOAuth2TokenServiceDelegate::BackoffEntry() const {
814   return &backoff_entry_;
815 }
816 
FixRequestErrorIfPossible()817 bool MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorIfPossible() {
818   return !fix_request_error_callback_.is_null()
819              ? fix_request_error_callback_.Run()
820              : false;
821 }
822 
AddAccountStatus(const CoreAccountId & account_id,const std::string & refresh_token,const GoogleServiceAuthError & error)823 void MutableProfileOAuth2TokenServiceDelegate::AddAccountStatus(
824     const CoreAccountId& account_id,
825     const std::string& refresh_token,
826     const GoogleServiceAuthError& error) {
827   DCHECK_EQ(0u, refresh_tokens_.count(account_id));
828   refresh_tokens_[account_id] = AccountStatus{refresh_token, error};
829   FireAuthErrorChanged(account_id, error);
830 }
831 
FinishLoadingCredentials()832 void MutableProfileOAuth2TokenServiceDelegate::FinishLoadingCredentials() {
833   if (account_consistency_ == signin::AccountConsistencyMethod::kDice)
834     DCHECK(client_->GetPrefs()->GetBoolean(prefs::kTokenServiceDiceCompatible));
835   FireRefreshTokensLoaded();
836 }
837 
RevokeCredentialsImpl(const CoreAccountId & account_id,bool revoke_on_server)838 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsImpl(
839     const CoreAccountId& account_id,
840     bool revoke_on_server) {
841   ValidateAccountId(account_id);
842   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
843 
844   if (refresh_tokens_.count(account_id) > 0) {
845     VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id;
846     ScopedBatchChange batch(this);
847     const std::string& token = refresh_tokens_[account_id].refresh_token;
848     if (revoke_on_server)
849       RevokeCredentialsOnServer(token);
850     refresh_tokens_.erase(account_id);
851     ClearPersistedCredentials(account_id);
852     FireRefreshTokenRevoked(account_id);
853   }
854 }
855 
MaybeDeletePreDiceTokens()856 void MutableProfileOAuth2TokenServiceDelegate::MaybeDeletePreDiceTokens() {
857   DCHECK(load_credentials_state() ==
858              signin::LoadCredentialsState::
859                  LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS ||
860          load_credentials_state() ==
861              signin::LoadCredentialsState::
862                  LOAD_CREDENTIALS_FINISHED_WITH_DB_CANNOT_BE_OPENED);
863 
864   if (account_consistency_ == signin::AccountConsistencyMethod::kDice &&
865       !client_->GetPrefs()->GetBoolean(prefs::kTokenServiceDiceCompatible)) {
866     RevokeAllCredentials();
867     client_->GetPrefs()->SetBoolean(prefs::kTokenServiceDiceCompatible, true);
868   }
869 }
870