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/webdata/common/web_data_service_base.h"
12 #include "google_apis/gaia/gaia_auth_fetcher.h"
13 #include "google_apis/gaia/gaia_auth_util.h"
14 #include "google_apis/gaia/gaia_constants.h"
15 #include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h"
16 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
17 #include "net/url_request/url_request_context_getter.h"
21 const char kAccountIdPrefix
[] = "AccountId-";
22 const size_t kAccountIdPrefixLength
= 10;
24 std::string
ApplyAccountIdPrefix(const std::string
& account_id
) {
25 return kAccountIdPrefix
+ account_id
;
28 bool IsLegacyRefreshTokenId(const std::string
& service_id
) {
29 return service_id
== GaiaConstants::kGaiaOAuth2LoginRefreshToken
;
32 bool IsLegacyServiceId(const std::string
& account_id
) {
33 return account_id
.compare(0u, kAccountIdPrefixLength
, kAccountIdPrefix
) != 0;
36 std::string
RemoveAccountIdPrefix(const std::string
& prefixed_account_id
) {
37 return prefixed_account_id
.substr(kAccountIdPrefixLength
);
42 // This class sends a request to GAIA to revoke the given refresh token from
43 // the server. This is a best effort attempt only. This class deletes itself
44 // when done sucessfully or otherwise.
45 class MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken
46 : public GaiaAuthConsumer
{
48 RevokeServerRefreshToken(
49 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate
,
50 const std::string
& account_id
);
51 ~RevokeServerRefreshToken() override
;
54 // GaiaAuthConsumer overrides:
55 void OnOAuth2RevokeTokenCompleted() override
;
57 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate_
;
58 GaiaAuthFetcher fetcher_
;
60 DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken
);
63 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
64 RevokeServerRefreshToken(
65 MutableProfileOAuth2TokenServiceDelegate
* token_service_delegate
,
66 const std::string
& refresh_token
)
67 : token_service_delegate_(token_service_delegate
),
69 GaiaConstants::kChromeSource
,
70 token_service_delegate_
->GetRequestContext()) {
71 fetcher_
.StartRevokeOAuth2Token(refresh_token
);
74 MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
75 ~RevokeServerRefreshToken() {
78 void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken::
79 OnOAuth2RevokeTokenCompleted() {
80 // |this| pointer will be deleted when removed from the vector, so don't
81 // access any members after call to erase().
82 token_service_delegate_
->server_revokes_
.erase(
83 std::find(token_service_delegate_
->server_revokes_
.begin(),
84 token_service_delegate_
->server_revokes_
.end(), this));
87 MutableProfileOAuth2TokenServiceDelegate::AccountInfo::AccountInfo(
88 SigninErrorController
* signin_error_controller
,
89 const std::string
& account_id
,
90 const std::string
& refresh_token
)
91 : signin_error_controller_(signin_error_controller
),
92 account_id_(account_id
),
93 refresh_token_(refresh_token
),
94 last_auth_error_(GoogleServiceAuthError::NONE
) {
95 DCHECK(signin_error_controller_
);
96 DCHECK(!account_id_
.empty());
97 signin_error_controller_
->AddProvider(this);
100 MutableProfileOAuth2TokenServiceDelegate::AccountInfo::~AccountInfo() {
101 signin_error_controller_
->RemoveProvider(this);
104 void MutableProfileOAuth2TokenServiceDelegate::AccountInfo::SetLastAuthError(
105 const GoogleServiceAuthError
& error
) {
106 if (error
.state() != last_auth_error_
.state()) {
107 last_auth_error_
= error
;
108 signin_error_controller_
->AuthStatusChanged();
113 MutableProfileOAuth2TokenServiceDelegate::AccountInfo::GetAccountId() const {
117 GoogleServiceAuthError
118 MutableProfileOAuth2TokenServiceDelegate::AccountInfo::GetAuthStatus() const {
119 return last_auth_error_
;
122 MutableProfileOAuth2TokenServiceDelegate::
123 MutableProfileOAuth2TokenServiceDelegate(
124 SigninClient
* client
,
125 SigninErrorController
* signin_error_controller
,
126 AccountTrackerService
* account_tracker_service
)
127 : web_data_service_request_(0),
128 backoff_entry_(&backoff_policy_
),
129 backoff_error_(GoogleServiceAuthError::NONE
),
131 signin_error_controller_(signin_error_controller
),
132 account_tracker_service_(account_tracker_service
) {
133 VLOG(1) << "MutablePO2TS::MutablePO2TS";
135 DCHECK(signin_error_controller
);
136 // It's okay to fill the backoff policy after being used in construction.
137 backoff_policy_
.num_errors_to_ignore
= 0;
138 backoff_policy_
.initial_delay_ms
= 1000;
139 backoff_policy_
.multiply_factor
= 2.0;
140 backoff_policy_
.jitter_factor
= 0.2;
141 backoff_policy_
.maximum_backoff_ms
= 15 * 60 * 1000;
142 backoff_policy_
.entry_lifetime_ms
= -1;
143 backoff_policy_
.always_use_initial_delay
= false;
146 MutableProfileOAuth2TokenServiceDelegate::
147 ~MutableProfileOAuth2TokenServiceDelegate() {
148 VLOG(1) << "MutablePO2TS::~MutablePO2TS";
149 DCHECK(server_revokes_
.empty());
152 OAuth2AccessTokenFetcher
*
153 MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
154 const std::string
& account_id
,
155 net::URLRequestContextGetter
* getter
,
156 OAuth2AccessTokenConsumer
* consumer
) {
157 ValidateAccountId(account_id
);
158 // check whether the account has persistent error.
159 if (refresh_tokens_
[account_id
]->GetAuthStatus().IsPersistentError()) {
160 VLOG(1) << "Request for token has been rejected due to persistent error #"
161 << refresh_tokens_
[account_id
]->GetAuthStatus().state();
162 return new OAuth2AccessTokenFetcherImmediateError(
163 consumer
, refresh_tokens_
[account_id
]->GetAuthStatus());
165 if (backoff_entry_
.ShouldRejectRequest()) {
166 VLOG(1) << "Request for token has been rejected due to backoff rules from"
167 << " previous error #" << backoff_error_
.state();
168 return new OAuth2AccessTokenFetcherImmediateError(consumer
, backoff_error_
);
170 std::string refresh_token
= GetRefreshToken(account_id
);
171 DCHECK(!refresh_token
.empty());
172 return new OAuth2AccessTokenFetcherImpl(consumer
, getter
, refresh_token
);
175 bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenHasError(
176 const std::string
& account_id
) const {
177 auto it
= refresh_tokens_
.find(account_id
);
178 return it
== refresh_tokens_
.end() ? false
179 : IsError(it
->second
->GetAuthStatus());
182 void MutableProfileOAuth2TokenServiceDelegate::UpdateAuthError(
183 const std::string
& account_id
,
184 const GoogleServiceAuthError
& error
) {
185 VLOG(1) << "MutablePO2TS::UpdateAuthError. Error: " << error
.state();
186 backoff_entry_
.InformOfRequest(!error
.IsTransientError());
187 ValidateAccountId(account_id
);
189 // Do not report connection errors as these are not actually auth errors.
190 // We also want to avoid masking a "real" auth error just because we
191 // subsequently get a transient network error. We do keep it around though
192 // to report for future requests being denied for "backoff" reasons.
193 if (error
.IsTransientError()) {
194 backoff_error_
= error
;
198 if (refresh_tokens_
.count(account_id
) == 0) {
199 // This could happen if the preferences have been corrupted (see
200 // http://crbug.com/321370). In a Debug build that would be a bug, but in a
201 // Release build we want to deal with it gracefully.
205 refresh_tokens_
[account_id
]->SetLastAuthError(error
);
208 bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
209 const std::string
& account_id
) const {
210 VLOG(1) << "MutablePO2TS::RefreshTokenIsAvailable";
211 return !GetRefreshToken(account_id
).empty();
214 std::string
MutableProfileOAuth2TokenServiceDelegate::GetRefreshToken(
215 const std::string
& account_id
) const {
216 AccountInfoMap::const_iterator iter
= refresh_tokens_
.find(account_id
);
217 if (iter
!= refresh_tokens_
.end())
218 return iter
->second
->refresh_token();
219 return std::string();
222 std::vector
<std::string
>
223 MutableProfileOAuth2TokenServiceDelegate::GetAccounts() {
224 std::vector
<std::string
> account_ids
;
225 for (AccountInfoMap::const_iterator iter
= refresh_tokens_
.begin();
226 iter
!= refresh_tokens_
.end(); ++iter
) {
227 account_ids
.push_back(iter
->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 AccountInfo(
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 (AccountInfoMap::const_iterator i
= refresh_tokens_
.begin();
294 i
!= refresh_tokens_
.end(); ++i
) {
295 if (!RefreshTokenIsAvailable(i
->first
)) {
296 UpdateAuthError(i
->first
,
297 GoogleServiceAuthError(
298 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
));
303 loading_primary_account_id_
.clear();
306 void MutableProfileOAuth2TokenServiceDelegate::LoadAllCredentialsIntoMemory(
307 const std::map
<std::string
, std::string
>& db_tokens
) {
308 std::string old_login_token
;
311 ScopedBatchChange
batch(this);
313 VLOG(1) << "MutablePO2TS::LoadAllCredentialsIntoMemory; "
314 << db_tokens
.size() << " Credential(s).";
315 AccountTrackerService::AccountIdMigrationState migration_state
=
316 account_tracker_service_
->GetMigrationState();
317 for (std::map
<std::string
, std::string
>::const_iterator iter
=
319 iter
!= db_tokens
.end(); ++iter
) {
320 std::string prefixed_account_id
= iter
->first
;
321 std::string refresh_token
= iter
->second
;
323 if (IsLegacyRefreshTokenId(prefixed_account_id
) && !refresh_token
.empty())
324 old_login_token
= refresh_token
;
326 if (IsLegacyServiceId(prefixed_account_id
)) {
327 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
328 if (token_web_data
.get())
329 token_web_data
->RemoveTokenForService(prefixed_account_id
);
331 DCHECK(!refresh_token
.empty());
332 std::string account_id
= RemoveAccountIdPrefix(prefixed_account_id
);
334 if (migration_state
== AccountTrackerService::MIGRATION_IN_PROGRESS
) {
335 // Migrate to gaia-ids.
336 AccountTrackerService::AccountInfo account_info
=
337 account_tracker_service_
->FindAccountInfoByEmail(account_id
);
338 // |account_info.gaia| could be empty if |account_id| is already gaia
339 // id. This could happen if the chrome was closed in the middle of
341 if (!account_info
.gaia
.empty()) {
342 PersistCredentials(account_info
.gaia
, refresh_token
);
343 ClearPersistedCredentials(account_id
);
344 account_id
= account_info
.gaia
;
347 // Skip duplicate accounts, this could happen if migration was
348 // crashed in the middle.
349 if (refresh_tokens_
.count(account_id
) != 0)
353 // If the account_id is an email address, then canonicalize it. This
354 // is to support legacy account_ids, and will not be needed after
355 // switching to gaia-ids.
356 if (account_id
.find('@') != std::string::npos
) {
357 // If the canonical account id is not the same as the loaded
358 // account id, make sure not to overwrite a refresh token from
359 // a canonical version. If no canonical version was loaded, then
360 // re-persist this refresh token with the canonical account id.
361 std::string canon_account_id
= gaia::CanonicalizeEmail(account_id
);
362 if (canon_account_id
!= account_id
) {
363 ClearPersistedCredentials(account_id
);
364 if (db_tokens
.count(ApplyAccountIdPrefix(canon_account_id
)) == 0)
365 PersistCredentials(canon_account_id
, refresh_token
);
368 account_id
= canon_account_id
;
371 refresh_tokens_
[account_id
].reset(new AccountInfo(
372 signin_error_controller_
, account_id
, refresh_token
));
373 FireRefreshTokenAvailable(account_id
);
377 if (!old_login_token
.empty()) {
378 DCHECK(!loading_primary_account_id_
.empty());
379 if (refresh_tokens_
.count(loading_primary_account_id_
) == 0)
380 UpdateCredentials(loading_primary_account_id_
, old_login_token
);
384 FireRefreshTokensLoaded();
387 void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentials(
388 const std::string
& account_id
,
389 const std::string
& refresh_token
) {
390 DCHECK(thread_checker_
.CalledOnValidThread());
391 DCHECK(!account_id
.empty());
392 DCHECK(!refresh_token
.empty());
393 ValidateAccountId(account_id
);
395 signin_metrics::LogSigninAddAccount();
397 bool refresh_token_present
= refresh_tokens_
.count(account_id
) > 0;
398 if (!refresh_token_present
||
399 refresh_tokens_
[account_id
]->refresh_token() != refresh_token
) {
400 ScopedBatchChange
batch(this);
402 // If token present, and different from the new one, cancel its requests,
403 // and clear the entries in cache related to that account.
404 if (refresh_token_present
) {
405 VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. "
406 << "account_id=" << account_id
;
408 refresh_tokens_
[account_id
]->set_refresh_token(refresh_token
);
410 VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. "
411 << "account_id=" << account_id
;
412 refresh_tokens_
[account_id
].reset(
413 new AccountInfo(signin_error_controller_
, account_id
, refresh_token
));
416 // Save the token in memory and in persistent store.
417 PersistCredentials(account_id
, refresh_token
);
419 UpdateAuthError(account_id
, GoogleServiceAuthError::AuthErrorNone());
420 FireRefreshTokenAvailable(account_id
);
424 void MutableProfileOAuth2TokenServiceDelegate::PersistCredentials(
425 const std::string
& account_id
,
426 const std::string
& refresh_token
) {
427 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
428 if (token_web_data
.get()) {
429 VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id
;
430 token_web_data
->SetTokenForService(ApplyAccountIdPrefix(account_id
),
435 void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
436 if (!client_
->CanRevokeCredentials())
438 DCHECK(thread_checker_
.CalledOnValidThread());
440 ScopedBatchChange
batch(this);
442 VLOG(1) << "MutablePO2TS::RevokeAllCredentials";
443 CancelWebTokenFetch();
444 AccountInfoMap tokens
= refresh_tokens_
;
445 for (AccountInfoMap::iterator i
= tokens
.begin(); i
!= tokens
.end(); ++i
)
446 RevokeCredentials(i
->first
);
448 DCHECK_EQ(0u, refresh_tokens_
.size());
450 // Make sure all tokens are removed.
451 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
452 if (token_web_data
.get())
453 token_web_data
->RemoveAllTokens();
456 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials(
457 const std::string
& account_id
) {
458 ValidateAccountId(account_id
);
459 DCHECK(thread_checker_
.CalledOnValidThread());
461 if (refresh_tokens_
.count(account_id
) > 0) {
462 VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id
;
463 ScopedBatchChange
batch(this);
464 RevokeCredentialsOnServer(refresh_tokens_
[account_id
]->refresh_token());
465 refresh_tokens_
.erase(account_id
);
466 ClearPersistedCredentials(account_id
);
467 FireRefreshTokenRevoked(account_id
);
471 void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials(
472 const std::string
& account_id
) {
473 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
474 if (token_web_data
.get()) {
475 VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id="
477 token_web_data
->RemoveTokenForService(ApplyAccountIdPrefix(account_id
));
481 void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer(
482 const std::string
& refresh_token
) {
483 // Keep track or all server revoke requests. This way they can be deleted
484 // before the token service is shutdown and won't outlive the profile.
485 server_revokes_
.push_back(new RevokeServerRefreshToken(this, refresh_token
));
488 void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() {
489 if (web_data_service_request_
!= 0) {
490 scoped_refptr
<TokenWebData
> token_web_data
= client_
->GetDatabase();
491 DCHECK(token_web_data
.get());
492 token_web_data
->CancelRequest(web_data_service_request_
);
493 web_data_service_request_
= 0;
497 void MutableProfileOAuth2TokenServiceDelegate::Shutdown() {
498 VLOG(1) << "MutablePO2TS::Shutdown";
499 server_revokes_
.clear();
500 CancelWebTokenFetch();
501 refresh_tokens_
.clear();