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.
5 #include "chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.h"
7 #include "base/profiler/scoped_tracker.h"
8 #include "components/signin/core/browser/signin_client.h"
9 #include "components/signin/core/browser/signin_metrics.h"
10 #include "components/signin/core/browser/webdata/token_web_data.h"
11 #include "components/signin/core/common/profile_management_switches.h"
12 #include "components/webdata/common/web_data_service_base.h"
13 #include "google_apis/gaia/gaia_auth_fetcher.h"
14 #include "google_apis/gaia/gaia_auth_util.h"
15 #include "google_apis/gaia/gaia_constants.h"
16 #include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"
17 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
18 #include "net/url_request/url_request_context_getter.h"
22 const char kAccountIdPrefix
[] = "AccountId-";
23 const size_t kAccountIdPrefixLength
= 10;
25 std::string
ApplyAccountIdPrefix(const std::string
& account_id
) {
26 return kAccountIdPrefix
+ account_id
;
29 bool IsLegacyRefreshTokenId(const std::string
& service_id
) {
30 return service_id
== GaiaConstants::kGaiaOAuth2LoginRefreshToken
;
33 bool IsLegacyServiceId(const std::string
& account_id
) {
34 return account_id
.compare(0u, kAccountIdPrefixLength
, kAccountIdPrefix
) != 0;
37 std::string
RemoveAccountIdPrefix(const std::string
& prefixed_account_id
) {
38 return prefixed_account_id
.substr(kAccountIdPrefixLength
);
43 // This class sends a request to GAIA to revoke the given refresh token from
44 // the server. This is a best effort attempt only. This class deletes itself
45 // when done sucessfully or otherwise.
46 class MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken
47 : public GaiaAuthConsumer
{
49 RevokeServerRefreshToken(
50 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate
,
51 const std::string
& account_id
);
52 ~RevokeServerRefreshToken() override
;
55 // GaiaAuthConsumer overrides:
56 void OnOAuth2RevokeTokenCompleted() override
;
58 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate_
;
59 GaiaAuthFetcher fetcher_
;
61 DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken
);
64 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
65 RevokeServerRefreshToken(
66 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate
,
67 const std::string
& refresh_token
)
68 : token_service_delegate_(token_service_delegate
),
70 GaiaConstants::kChromeSource
,
71 token_service_delegate_
->GetRequestContext()) {
72 fetcher_
.StartRevokeOAuth2Token(refresh_token
);
75 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
76 ~RevokeServerRefreshToken() {
79 void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
80 OnOAuth2RevokeTokenCompleted() {
81 // |this| pointer will be deleted when removed from the vector, so don't
82 // access any members after call to erase().
83 token_service_delegate_
->server_revokes_
.erase(
84 std::find(token_service_delegate_
->server_revokes_
.begin(),
85 token_service_delegate_
->server_revokes_
.end(), this));
88 MutableProfileOAuth2TokenServiceDelegate::AccountStatus::AccountStatus(
89 SigninErrorController
* signin_error_controller
,
90 const std::string
& account_id
,
91 const std::string
& refresh_token
)
92 : signin_error_controller_(signin_error_controller
),
93 account_id_(account_id
),
94 refresh_token_(refresh_token
),
95 last_auth_error_(GoogleServiceAuthError::NONE
) {
96 DCHECK(signin_error_controller_
);
97 DCHECK(!account_id_
.empty());
98 signin_error_controller_
->AddProvider(this);
101 MutableProfileOAuth2TokenServiceDelegate::AccountStatus::~AccountStatus() {
102 signin_error_controller_
->RemoveProvider(this);
105 void MutableProfileOAuth2TokenServiceDelegate::AccountStatus::SetLastAuthError(
106 const GoogleServiceAuthError
& error
) {
107 if (error
.state() != last_auth_error_
.state()) {
108 last_auth_error_
= error
;
109 signin_error_controller_
->AuthStatusChanged();
114 MutableProfileOAuth2TokenServiceDelegate::AccountStatus::GetAccountId() const {
118 GoogleServiceAuthError
119 MutableProfileOAuth2TokenServiceDelegate::AccountStatus::GetAuthStatus() const {
120 return last_auth_error_
;
123 MutableProfileOAuth2TokenServiceDelegate::
124 MutableProfileOAuth2TokenServiceDelegate(
125 SigninClient
* client
,
126 SigninErrorController
* signin_error_controller
,
127 AccountTrackerService
* account_tracker_service
)
128 : web_data_service_request_(0),
129 backoff_entry_(&backoff_policy_
),
130 backoff_error_(GoogleServiceAuthError::NONE
),
132 signin_error_controller_(signin_error_controller
),
133 account_tracker_service_(account_tracker_service
) {
134 VLOG(1) << "MutablePO2TS::MutablePO2TS";
136 DCHECK(signin_error_controller
);
137 // It's okay to fill the backoff policy after being used in construction.
138 backoff_policy_
.num_errors_to_ignore
= 0;
139 backoff_policy_
.initial_delay_ms
= 1000;
140 backoff_policy_
.multiply_factor
= 2.0;
141 backoff_policy_
.jitter_factor
= 0.2;
142 backoff_policy_
.maximum_backoff_ms
= 15 * 60 * 1000;
143 backoff_policy_
.entry_lifetime_ms
= -1;
144 backoff_policy_
.always_use_initial_delay
= false;
147 MutableProfileOAuth2TokenServiceDelegate::
148 ~MutableProfileOAuth2TokenServiceDelegate() {
149 VLOG(1) << "MutablePO2TS::~MutablePO2TS";
150 DCHECK(server_revokes_
.empty());
153 OAuth2AccessTokenFetcher
*
154 MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
155 const std::string
& account_id
,
156 net::URLRequestContextGetter
* getter
,
157 OAuth2AccessTokenConsumer
* consumer
) {
158 ValidateAccountId(account_id
);
159 // check whether the account has persistent error.
160 if (refresh_tokens_
[account_id
]->GetAuthStatus().IsPersistentError()) {
161 VLOG(1) << "Request for token has been rejected due to persistent error #"
162 << refresh_tokens_
[account_id
]->GetAuthStatus().state();
163 return new OAuth2AccessTokenFetcherImmediateError(
164 consumer
, refresh_tokens_
[account_id
]->GetAuthStatus());
166 if (backoff_entry_
.ShouldRejectRequest()) {
167 VLOG(1) << "Request for token has been rejected due to backoff rules from"
168 << " previous error #" << backoff_error_
.state();
169 return new OAuth2AccessTokenFetcherImmediateError(consumer
, backoff_error_
);
171 std::string refresh_token
= GetRefreshToken(account_id
);
172 DCHECK(!refresh_token
.empty());
173 return new OAuth2AccessTokenFetcherImpl(consumer
, getter
, refresh_token
);
176 bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenHasError(
177 const std::string
& account_id
) const {
178 auto it
= refresh_tokens_
.find(account_id
);
179 return it
== refresh_tokens_
.end() ? false
180 : IsError(it
->second
->GetAuthStatus());
183 void MutableProfileOAuth2TokenServiceDelegate::UpdateAuthError(
184 const std::string
& account_id
,
185 const GoogleServiceAuthError
& error
) {
186 VLOG(1) << "MutablePO2TS::UpdateAuthError. Error: " << error
.state();
187 backoff_entry_
.InformOfRequest(!error
.IsTransientError());
188 ValidateAccountId(account_id
);
190 // Do not report connection errors as these are not actually auth errors.
191 // We also want to avoid masking a "real" auth error just because we
192 // subsequently get a transient network error. We do keep it around though
193 // to report for future requests being denied for "backoff" reasons.
194 if (error
.IsTransientError()) {
195 backoff_error_
= error
;
199 if (refresh_tokens_
.count(account_id
) == 0) {
200 // This could happen if the preferences have been corrupted (see
201 // http://crbug.com/321370). In a Debug build that would be a bug, but in a
202 // Release build we want to deal with it gracefully.
206 refresh_tokens_
[account_id
]->SetLastAuthError(error
);
209 bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
210 const std::string
& account_id
) const {
211 VLOG(1) << "MutablePO2TS::RefreshTokenIsAvailable";
212 return !GetRefreshToken(account_id
).empty();
215 std::string
MutableProfileOAuth2TokenServiceDelegate::GetRefreshToken(
216 const std::string
& account_id
) const {
217 AccountStatusMap::const_iterator iter
= refresh_tokens_
.find(account_id
);
218 if (iter
!= refresh_tokens_
.end())
219 return iter
->second
->refresh_token();
220 return std::string();
223 std::vector
<std::string
>
224 MutableProfileOAuth2TokenServiceDelegate::GetAccounts() {
225 std::vector
<std::string
> account_ids
;
226 for (auto& token
: refresh_tokens_
) {
227 account_ids
.push_back(token
.first
);
232 net::URLRequestContextGetter
*
233 MutableProfileOAuth2TokenServiceDelegate::GetRequestContext() const {
234 return client_
->GetURLRequestContext();
237 void MutableProfileOAuth2TokenServiceDelegate::LoadCredentials(
238 const std::string
& primary_account_id
) {
239 DCHECK(!primary_account_id
.empty());
240 ValidateAccountId(primary_account_id
);
241 DCHECK(loading_primary_account_id_
.empty());
242 DCHECK_EQ(0, web_data_service_request_
);
244 refresh_tokens_
.clear();
246 // If the account_id is an email address, then canonicalize it. This
247 // is to support legacy account_ids, and will not be needed after
248 // switching to gaia-ids.
249 if (primary_account_id
.find('@') != std::string::npos
) {
250 loading_primary_account_id_
= gaia::CanonicalizeEmail(primary_account_id
);
252 loading_primary_account_id_
= primary_account_id
;
255 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
256 if (token_web_data
.get())
257 web_data_service_request_
= token_web_data
->GetAllTokens(this);
260 void MutableProfileOAuth2TokenServiceDelegate::OnWebDataServiceRequestDone(
261 WebDataServiceBase::Handle handle
,
262 const WDTypedResult
* result
) {
263 VLOG(1) << "MutablePO2TS::OnWebDataServiceRequestDone. Result type: "
264 << (result
== nullptr ? -1 : (int)result
->GetType());
266 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
268 tracked_objects::ScopedTracker
tracking_profile(
269 FROM_HERE_WITH_EXPLICIT_FUNCTION(
270 "422460 MutableProfileOAuth2Token...::OnWebDataServiceRequestDone"));
272 DCHECK_EQ(web_data_service_request_
, handle
);
273 web_data_service_request_
= 0;
276 DCHECK(result
->GetType() == TOKEN_RESULT
);
277 const WDResult
<std::map
<std::string
, std::string
>>* token_result
=
278 static_cast<const WDResult
<std::map
<std::string
, std::string
>>*>(
280 LoadAllCredentialsIntoMemory(token_result
->GetValue());
283 // Make sure that we have an entry for |loading_primary_account_id_| in the
284 // map. The entry could be missing if there is a corruption in the token DB
285 // while this profile is connected to an account.
286 DCHECK(!loading_primary_account_id_
.empty());
287 if (refresh_tokens_
.count(loading_primary_account_id_
) == 0) {
288 refresh_tokens_
[loading_primary_account_id_
].reset(new AccountStatus(
289 signin_error_controller_
, loading_primary_account_id_
, std::string()));
292 // If we don't have a refresh token for a known account, signal an error.
293 for (auto& token
: refresh_tokens_
) {
294 if (!RefreshTokenIsAvailable(token
.first
)) {
295 UpdateAuthError(token
.first
,
296 GoogleServiceAuthError(
297 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
));
302 loading_primary_account_id_
.clear();
305 void MutableProfileOAuth2TokenServiceDelegate::LoadAllCredentialsIntoMemory(
306 const std::map
<std::string
, std::string
>& db_tokens
) {
307 std::string old_login_token
;
310 ScopedBatchChange
batch(this);
312 VLOG(1) << "MutablePO2TS::LoadAllCredentialsIntoMemory; "
313 << db_tokens
.size() << " Credential(s).";
314 AccountTrackerService::AccountIdMigrationState migration_state
=
315 account_tracker_service_
->GetMigrationState();
316 for (std::map
<std::string
, std::string
>::const_iterator iter
=
318 iter
!= db_tokens
.end(); ++iter
) {
319 std::string prefixed_account_id
= iter
->first
;
320 std::string refresh_token
= iter
->second
;
322 if (IsLegacyRefreshTokenId(prefixed_account_id
) && !refresh_token
.empty())
323 old_login_token
= refresh_token
;
325 if (IsLegacyServiceId(prefixed_account_id
)) {
326 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
327 if (token_web_data
.get())
328 token_web_data
->RemoveTokenForService(prefixed_account_id
);
330 DCHECK(!refresh_token
.empty());
331 std::string account_id
= RemoveAccountIdPrefix(prefixed_account_id
);
333 if (migration_state
== AccountTrackerService::MIGRATION_IN_PROGRESS
) {
334 // Migrate to gaia-ids.
335 AccountInfo account_info
=
336 account_tracker_service_
->FindAccountInfoByEmail(account_id
);
337 // |account_info.gaia| could be empty if |account_id| is already gaia
338 // id. This could happen if the chrome was closed in the middle of
340 if (!account_info
.gaia
.empty()) {
341 PersistCredentials(account_info
.gaia
, refresh_token
);
342 ClearPersistedCredentials(account_id
);
343 account_id
= account_info
.gaia
;
346 // Skip duplicate accounts, this could happen if migration was
347 // crashed in the middle.
348 if (refresh_tokens_
.count(account_id
) != 0)
352 // If the account_id is an email address, then canonicalize it. This
353 // is to support legacy account_ids, and will not be needed after
354 // switching to gaia-ids.
355 if (account_id
.find('@') != std::string::npos
) {
356 // If the canonical account id is not the same as the loaded
357 // account id, make sure not to overwrite a refresh token from
358 // a canonical version. If no canonical version was loaded, then
359 // re-persist this refresh token with the canonical account id.
360 std::string canon_account_id
= gaia::CanonicalizeEmail(account_id
);
361 if (canon_account_id
!= account_id
) {
362 ClearPersistedCredentials(account_id
);
363 if (db_tokens
.count(ApplyAccountIdPrefix(canon_account_id
)) == 0)
364 PersistCredentials(canon_account_id
, refresh_token
);
367 account_id
= canon_account_id
;
370 // Only load secondary accounts when account consistency is enabled.
371 if (switches::IsEnableAccountConsistency() ||
372 account_id
== loading_primary_account_id_
) {
373 refresh_tokens_
[account_id
].reset(new AccountStatus(
374 signin_error_controller_
, account_id
, refresh_token
));
375 FireRefreshTokenAvailable(account_id
);
377 RevokeCredentialsOnServer(refresh_token
);
378 ClearPersistedCredentials(account_id
);
379 FireRefreshTokenRevoked(account_id
);
384 if (!old_login_token
.empty()) {
385 DCHECK(!loading_primary_account_id_
.empty());
386 if (refresh_tokens_
.count(loading_primary_account_id_
) == 0)
387 UpdateCredentials(loading_primary_account_id_
, old_login_token
);
391 FireRefreshTokensLoaded();
394 void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentials(
395 const std::string
& account_id
,
396 const std::string
& refresh_token
) {
397 DCHECK(thread_checker_
.CalledOnValidThread());
398 DCHECK(!account_id
.empty());
399 DCHECK(!refresh_token
.empty());
400 ValidateAccountId(account_id
);
402 signin_metrics::LogSigninAddAccount();
404 bool refresh_token_present
= refresh_tokens_
.count(account_id
) > 0;
405 if (!refresh_token_present
||
406 refresh_tokens_
[account_id
]->refresh_token() != refresh_token
) {
407 ScopedBatchChange
batch(this);
409 // If token present, and different from the new one, cancel its requests,
410 // and clear the entries in cache related to that account.
411 if (refresh_token_present
) {
412 VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. "
413 << "account_id=" << account_id
;
415 refresh_tokens_
[account_id
]->set_refresh_token(refresh_token
);
417 VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. "
418 << "account_id=" << account_id
;
419 refresh_tokens_
[account_id
].reset(new AccountStatus(
420 signin_error_controller_
, account_id
, refresh_token
));
423 // Save the token in memory and in persistent store.
424 PersistCredentials(account_id
, refresh_token
);
426 UpdateAuthError(account_id
, GoogleServiceAuthError::AuthErrorNone());
427 FireRefreshTokenAvailable(account_id
);
431 void MutableProfileOAuth2TokenServiceDelegate::PersistCredentials(
432 const std::string
& account_id
,
433 const std::string
& refresh_token
) {
434 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
435 if (token_web_data
.get()) {
436 VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id
;
437 token_web_data
->SetTokenForService(ApplyAccountIdPrefix(account_id
),
442 void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
443 if (!client_
->CanRevokeCredentials())
445 DCHECK(thread_checker_
.CalledOnValidThread());
447 ScopedBatchChange
batch(this);
449 VLOG(1) << "MutablePO2TS::RevokeAllCredentials";
450 CancelWebTokenFetch();
451 AccountStatusMap tokens
= refresh_tokens_
;
452 for (auto& token
: tokens
)
453 RevokeCredentials(token
.first
);
455 DCHECK_EQ(0u, refresh_tokens_
.size());
457 // Make sure all tokens are removed.
458 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
459 if (token_web_data
.get())
460 token_web_data
->RemoveAllTokens();
463 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials(
464 const std::string
& account_id
) {
465 ValidateAccountId(account_id
);
466 DCHECK(thread_checker_
.CalledOnValidThread());
468 if (refresh_tokens_
.count(account_id
) > 0) {
469 VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id
;
470 ScopedBatchChange
batch(this);
471 RevokeCredentialsOnServer(refresh_tokens_
[account_id
]->refresh_token());
472 refresh_tokens_
.erase(account_id
);
473 ClearPersistedCredentials(account_id
);
474 FireRefreshTokenRevoked(account_id
);
478 void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials(
479 const std::string
& account_id
) {
480 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
481 if (token_web_data
.get()) {
482 VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id="
484 token_web_data
->RemoveTokenForService(ApplyAccountIdPrefix(account_id
));
488 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer(
489 const std::string
& refresh_token
) {
490 // Keep track or all server revoke requests. This way they can be deleted
491 // before the token service is shutdown and won't outlive the profile.
492 server_revokes_
.push_back(new RevokeServerRefreshToken(this, refresh_token
));
495 void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() {
496 if (web_data_service_request_
!= 0) {
497 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
498 DCHECK(token_web_data
.get());
499 token_web_data
->CancelRequest(web_data_service_request_
);
500 web_data_service_request_
= 0;
504 void MutableProfileOAuth2TokenServiceDelegate::Shutdown() {
505 VLOG(1) << "MutablePO2TS::Shutdown";
506 server_revokes_
.clear();
507 CancelWebTokenFetch();
508 refresh_tokens_
.clear();