1 // Copyright 2014 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 "components/signin/core/browser/account_tracker_service.h"
7 #include "base/callback.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/metrics/field_trial.h"
11 #include "base/prefs/scoped_user_pref_update.h"
12 #include "base/profiler/scoped_tracker.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/trace_event/trace_event.h"
15 #include "components/signin/core/browser/refresh_token_annotation_request.h"
16 #include "components/signin/core/browser/signin_client.h"
17 #include "components/signin/core/browser/signin_manager.h"
18 #include "components/signin/core/common/signin_pref_names.h"
19 #include "components/signin/core/common/signin_switches.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_constants.h"
22 #include "google_apis/gaia/gaia_oauth_client.h"
23 #include "net/url_request/url_request_context_getter.h"
27 const char kAccountKeyPath
[] = "account_id";
28 const char kAccountEmailPath
[] = "email";
29 const char kAccountGaiaPath
[] = "gaia";
30 const char kAccountHostedDomainPath
[] = "hd";
32 #if !defined(OS_ANDROID) && !defined(OS_IOS)
33 // IsRefreshTokenDeviceIdExperimentEnabled is called from
34 // SendRefreshTokenAnnotationRequest only on desktop platforms.
35 bool IsRefreshTokenDeviceIdExperimentEnabled() {
36 const std::string group_name
=
37 base::FieldTrialList::FindFullName("RefreshTokenDeviceId");
38 return group_name
== "Enabled";
43 // This must be a string which can never be a valid domain.
44 const char AccountTrackerService::kNoHostedDomainFound
[] = "NO_HOSTED_DOMAIN";
46 class AccountInfoFetcher
: public OAuth2TokenService::Consumer
,
47 public gaia::GaiaOAuthClient::Delegate
{
49 AccountInfoFetcher(OAuth2TokenService
* token_service
,
50 net::URLRequestContextGetter
* request_context_getter
,
51 AccountTrackerService
* service
,
52 const std::string
& account_id
);
53 ~AccountInfoFetcher() override
;
55 const std::string
& account_id() { return account_id_
; }
59 // OAuth2TokenService::Consumer implementation.
60 void OnGetTokenSuccess(const OAuth2TokenService::Request
* request
,
61 const std::string
& access_token
,
62 const base::Time
& expiration_time
) override
;
63 void OnGetTokenFailure(const OAuth2TokenService::Request
* request
,
64 const GoogleServiceAuthError
& error
) override
;
66 // gaia::GaiaOAuthClient::Delegate implementation.
67 void OnGetUserInfoResponse(
68 scoped_ptr
<base::DictionaryValue
> user_info
) override
;
69 void OnOAuthError() override
;
70 void OnNetworkError(int response_code
) override
;
73 OAuth2TokenService
* token_service_
;
74 net::URLRequestContextGetter
* request_context_getter_
;
75 AccountTrackerService
* service_
;
76 const std::string account_id_
;
78 scoped_ptr
<OAuth2TokenService::Request
> login_token_request_
;
79 scoped_ptr
<gaia::GaiaOAuthClient
> gaia_oauth_client_
;
82 AccountInfoFetcher::AccountInfoFetcher(
83 OAuth2TokenService
* token_service
,
84 net::URLRequestContextGetter
* request_context_getter
,
85 AccountTrackerService
* service
,
86 const std::string
& account_id
)
87 : OAuth2TokenService::Consumer("gaia_account_tracker"),
88 token_service_(token_service
),
89 request_context_getter_(request_context_getter
),
91 account_id_(account_id
) {
92 TRACE_EVENT_ASYNC_BEGIN1(
93 "AccountTrackerService", "AccountIdFetcher", this,
94 "account_id", account_id
);
97 AccountInfoFetcher::~AccountInfoFetcher() {
98 TRACE_EVENT_ASYNC_END0("AccountTrackerService", "AccountIdFetcher", this);
101 void AccountInfoFetcher::Start() {
102 OAuth2TokenService::ScopeSet scopes
;
103 scopes
.insert(GaiaConstants::kGoogleUserInfoEmail
);
104 scopes
.insert(GaiaConstants::kGoogleUserInfoProfile
);
105 login_token_request_
= token_service_
->StartRequest(
106 account_id_
, scopes
, this);
109 void AccountInfoFetcher::OnGetTokenSuccess(
110 const OAuth2TokenService::Request
* request
,
111 const std::string
& access_token
,
112 const base::Time
& expiration_time
) {
113 TRACE_EVENT_ASYNC_STEP_PAST0(
114 "AccountTrackerService", "AccountIdFetcher", this, "OnGetTokenSuccess");
115 DCHECK_EQ(request
, login_token_request_
.get());
117 gaia_oauth_client_
.reset(new gaia::GaiaOAuthClient(request_context_getter_
));
119 const int kMaxRetries
= 3;
120 gaia_oauth_client_
->GetUserInfo(access_token
, kMaxRetries
, this);
123 void AccountInfoFetcher::OnGetTokenFailure(
124 const OAuth2TokenService::Request
* request
,
125 const GoogleServiceAuthError
& error
) {
126 TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
130 "google_service_auth_error",
132 LOG(ERROR
) << "OnGetTokenFailure: " << error
.ToString();
133 DCHECK_EQ(request
, login_token_request_
.get());
134 service_
->OnUserInfoFetchFailure(this);
137 void AccountInfoFetcher::OnGetUserInfoResponse(
138 scoped_ptr
<base::DictionaryValue
> user_info
) {
139 TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
142 "OnGetUserInfoResponse",
145 service_
->OnUserInfoFetchSuccess(this, user_info
.get());
148 void AccountInfoFetcher::OnOAuthError() {
149 TRACE_EVENT_ASYNC_STEP_PAST0(
150 "AccountTrackerService", "AccountIdFetcher", this, "OnOAuthError");
151 LOG(ERROR
) << "OnOAuthError";
152 service_
->OnUserInfoFetchFailure(this);
155 void AccountInfoFetcher::OnNetworkError(int response_code
) {
156 TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
162 LOG(ERROR
) << "OnNetworkError " << response_code
;
163 service_
->OnUserInfoFetchFailure(this);
166 AccountTrackerService::AccountInfo::AccountInfo() {}
167 AccountTrackerService::AccountInfo::~AccountInfo() {}
169 bool AccountTrackerService::AccountInfo::IsValid() {
170 return account_id
.empty() || email
.empty() || gaia
.empty() ||
171 hosted_domain
.empty();
175 const char AccountTrackerService::kAccountInfoPref
[] = "account_info";
177 AccountTrackerService::AccountTrackerService()
178 : token_service_(NULL
),
179 signin_client_(NULL
),
180 shutdown_called_(false),
181 network_fetches_enabled_(false) {
184 AccountTrackerService::~AccountTrackerService() {
185 DCHECK(shutdown_called_
);
188 void AccountTrackerService::Initialize(
189 OAuth2TokenService
* token_service
,
190 SigninClient
* signin_client
) {
191 DCHECK(token_service
);
192 DCHECK(!token_service_
);
193 DCHECK(signin_client
);
194 DCHECK(!signin_client_
);
195 token_service_
= token_service
;
196 signin_client_
= signin_client
;
197 token_service_
->AddObserver(this);
199 LoadFromTokenService();
202 void AccountTrackerService::EnableNetworkFetches() {
203 DCHECK(CalledOnValidThread());
204 DCHECK(!network_fetches_enabled_
);
205 network_fetches_enabled_
= true;
206 for (std::string account_id
: pending_user_info_fetches_
)
207 StartFetchingUserInfo(account_id
);
208 pending_user_info_fetches_
.clear();
211 void AccountTrackerService::Shutdown() {
212 shutdown_called_
= true;
213 STLDeleteValues(&user_info_requests_
);
214 token_service_
->RemoveObserver(this);
217 void AccountTrackerService::AddObserver(Observer
* observer
) {
218 observer_list_
.AddObserver(observer
);
221 void AccountTrackerService::RemoveObserver(Observer
* observer
) {
222 observer_list_
.RemoveObserver(observer
);
225 bool AccountTrackerService::IsAllUserInfoFetched() const {
226 return user_info_requests_
.empty();
229 std::vector
<AccountTrackerService::AccountInfo
>
230 AccountTrackerService::GetAccounts() const {
231 std::vector
<AccountInfo
> accounts
;
233 for (std::map
<std::string
, AccountState
>::const_iterator it
=
235 it
!= accounts_
.end();
237 const AccountState
& state
= it
->second
;
238 accounts
.push_back(state
.info
);
243 AccountTrackerService::AccountInfo
AccountTrackerService::GetAccountInfo(
244 const std::string
& account_id
) {
245 if (ContainsKey(accounts_
, account_id
))
246 return accounts_
[account_id
].info
;
248 return AccountInfo();
251 AccountTrackerService::AccountInfo
252 AccountTrackerService::FindAccountInfoByGaiaId(
253 const std::string
& gaia_id
) {
254 for (std::map
<std::string
, AccountState
>::const_iterator it
=
256 it
!= accounts_
.end();
258 const AccountState
& state
= it
->second
;
259 if (state
.info
.gaia
== gaia_id
)
263 return AccountInfo();
266 AccountTrackerService::AccountInfo
267 AccountTrackerService::FindAccountInfoByEmail(
268 const std::string
& email
) {
269 for (std::map
<std::string
, AccountState
>::const_iterator it
=
271 it
!= accounts_
.end();
273 const AccountState
& state
= it
->second
;
274 if (gaia::AreEmailsSame(state
.info
.email
, email
))
278 return AccountInfo();
281 AccountTrackerService::AccountIdMigrationState
282 AccountTrackerService::GetMigrationState() {
283 return GetMigrationState(signin_client_
->GetPrefs());
287 AccountTrackerService::AccountIdMigrationState
288 AccountTrackerService::GetMigrationState(PrefService
* pref_service
) {
289 return static_cast<AccountTrackerService::AccountIdMigrationState
>(
290 pref_service
->GetInteger(prefs::kAccountIdMigrationState
));
293 void AccountTrackerService::OnRefreshTokenAvailable(
294 const std::string
& account_id
) {
295 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
297 tracked_objects::ScopedTracker
tracking_profile(
298 FROM_HERE_WITH_EXPLICIT_FUNCTION(
299 "422460 AccountTrackerService::OnRefreshTokenAvailable"));
301 TRACE_EVENT1("AccountTrackerService",
302 "AccountTracker::OnRefreshTokenAvailable",
305 DVLOG(1) << "AVAILABLE " << account_id
;
307 StartTrackingAccount(account_id
);
308 AccountState
& state
= accounts_
[account_id
];
310 // Don't bother fetching for supervised users since this causes the token
311 // service to raise spurious auth errors.
312 // TODO(treib): this string is also used in supervised_user_constants.cc.
313 // Should put in a common place.
314 if (account_id
== "managed_user@localhost")
317 #if defined(OS_ANDROID)
318 // TODO(mlerman): Change this condition back to state.info.IsValid() and
319 // ensure the Fetch doesn't occur until after ProfileImpl::OnPrefsLoaded().
320 if (state
.info
.gaia
.empty())
322 if (state
.info
.IsValid())
324 StartFetchingUserInfo(account_id
);
326 SendRefreshTokenAnnotationRequest(account_id
);
329 void AccountTrackerService::OnRefreshTokenRevoked(
330 const std::string
& account_id
) {
331 TRACE_EVENT1("AccountTrackerService",
332 "AccountTracker::OnRefreshTokenRevoked",
336 DVLOG(1) << "REVOKED " << account_id
;
337 StopTrackingAccount(account_id
);
340 void AccountTrackerService::NotifyAccountUpdated(const AccountState
& state
) {
341 DCHECK(!state
.info
.gaia
.empty());
343 Observer
, observer_list_
, OnAccountUpdated(state
.info
));
346 void AccountTrackerService::NotifyAccountUpdateFailed(
347 const std::string
& account_id
) {
349 Observer
, observer_list_
, OnAccountUpdateFailed(account_id
));
352 void AccountTrackerService::NotifyAccountRemoved(const AccountState
& state
) {
353 DCHECK(!state
.info
.gaia
.empty());
355 Observer
, observer_list_
, OnAccountRemoved(state
.info
));
358 void AccountTrackerService::StartTrackingAccount(
359 const std::string
& account_id
) {
360 if (!ContainsKey(accounts_
, account_id
)) {
361 DVLOG(1) << "StartTracking " << account_id
;
363 state
.info
.account_id
= account_id
;
364 accounts_
.insert(make_pair(account_id
, state
));
368 void AccountTrackerService::StopTrackingAccount(const std::string
& account_id
) {
369 DVLOG(1) << "StopTracking " << account_id
;
370 if (ContainsKey(accounts_
, account_id
)) {
371 AccountState
& state
= accounts_
[account_id
];
372 RemoveFromPrefs(state
);
373 if (!state
.info
.gaia
.empty())
374 NotifyAccountRemoved(state
);
376 accounts_
.erase(account_id
);
379 if (ContainsKey(user_info_requests_
, account_id
))
380 DeleteFetcher(user_info_requests_
[account_id
]);
383 void AccountTrackerService::StartFetchingUserInfo(
384 const std::string
& account_id
) {
385 DCHECK(CalledOnValidThread());
386 if (!network_fetches_enabled_
) {
387 pending_user_info_fetches_
.push_back(account_id
);
391 if (ContainsKey(user_info_requests_
, account_id
))
392 DeleteFetcher(user_info_requests_
[account_id
]);
394 DVLOG(1) << "StartFetching " << account_id
;
395 AccountInfoFetcher
* fetcher
=
396 new AccountInfoFetcher(token_service_
,
397 signin_client_
->GetURLRequestContext(),
400 user_info_requests_
[account_id
] = fetcher
;
404 void AccountTrackerService::SetAccountStateFromUserInfo(
405 const std::string
& account_id
,
406 const base::DictionaryValue
* user_info
) {
407 AccountState
& state
= accounts_
[account_id
];
411 if (user_info
->GetString("id", &gaia_id
) &&
412 user_info
->GetString("email", &email
)) {
413 state
.info
.gaia
= gaia_id
;
414 state
.info
.email
= email
;
416 std::string hosted_domain
;
417 if (user_info
->GetString("hd", &hosted_domain
) && !hosted_domain
.empty()) {
418 state
.info
.hosted_domain
= hosted_domain
;
420 state
.info
.hosted_domain
= kNoHostedDomainFound
;
423 NotifyAccountUpdated(state
);
428 void AccountTrackerService::OnUserInfoFetchSuccess(
429 AccountInfoFetcher
* fetcher
,
430 const base::DictionaryValue
* user_info
) {
431 const std::string
& account_id
= fetcher
->account_id();
432 DCHECK(ContainsKey(accounts_
, account_id
));
434 SetAccountStateFromUserInfo(account_id
, user_info
);
435 DeleteFetcher(fetcher
);
438 void AccountTrackerService::OnUserInfoFetchFailure(
439 AccountInfoFetcher
* fetcher
) {
440 LOG(WARNING
) << "Failed to get UserInfo for " << fetcher
->account_id();
441 NotifyAccountUpdateFailed(fetcher
->account_id());
442 DeleteFetcher(fetcher
);
443 // TODO(rogerta): figure out when to retry.
446 void AccountTrackerService::DeleteFetcher(AccountInfoFetcher
* fetcher
) {
447 DVLOG(1) << "DeleteFetcher " << fetcher
->account_id();
448 const std::string
& account_id
= fetcher
->account_id();
449 DCHECK(ContainsKey(user_info_requests_
, account_id
));
450 DCHECK_EQ(fetcher
, user_info_requests_
[account_id
]);
451 user_info_requests_
.erase(account_id
);
455 void AccountTrackerService::LoadFromPrefs() {
456 const base::ListValue
* list
=
457 signin_client_
->GetPrefs()->GetList(kAccountInfoPref
);
458 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
459 const base::DictionaryValue
* dict
;
460 if (list
->GetDictionary(i
, &dict
)) {
461 base::string16 value
;
462 if (dict
->GetString(kAccountKeyPath
, &value
)) {
463 std::string account_id
= base::UTF16ToUTF8(value
);
464 StartTrackingAccount(account_id
);
465 AccountState
& state
= accounts_
[account_id
];
467 if (dict
->GetString(kAccountGaiaPath
, &value
))
468 state
.info
.gaia
= base::UTF16ToUTF8(value
);
469 if (dict
->GetString(kAccountEmailPath
, &value
))
470 state
.info
.email
= base::UTF16ToUTF8(value
);
471 if (dict
->GetString(kAccountHostedDomainPath
, &value
))
472 state
.info
.hosted_domain
= base::UTF16ToUTF8(value
);
473 if (!state
.info
.IsValid())
474 NotifyAccountUpdated(state
);
480 void AccountTrackerService::SaveToPrefs(const AccountState
& state
) {
481 if (!signin_client_
->GetPrefs())
484 base::DictionaryValue
* dict
= NULL
;
485 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
486 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
487 for(size_t i
= 0; i
< update
->GetSize(); ++i
, dict
= NULL
) {
488 if (update
->GetDictionary(i
, &dict
)) {
489 base::string16 value
;
490 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
)
496 dict
= new base::DictionaryValue();
497 update
->Append(dict
); // |update| takes ownership.
498 dict
->SetString(kAccountKeyPath
, account_id_16
);
501 dict
->SetString(kAccountEmailPath
, state
.info
.email
);
502 dict
->SetString(kAccountGaiaPath
, state
.info
.gaia
);
503 dict
->SetString(kAccountHostedDomainPath
, state
.info
.hosted_domain
);
506 void AccountTrackerService::RemoveFromPrefs(const AccountState
& state
) {
507 if (!signin_client_
->GetPrefs())
510 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
511 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
512 for(size_t i
= 0; i
< update
->GetSize(); ++i
) {
513 base::DictionaryValue
* dict
= NULL
;
514 if (update
->GetDictionary(i
, &dict
)) {
515 base::string16 value
;
516 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
) {
517 update
->Remove(i
, NULL
);
524 void AccountTrackerService::LoadFromTokenService() {
525 std::vector
<std::string
> accounts
= token_service_
->GetAccounts();
526 for (std::vector
<std::string
>::const_iterator it
= accounts
.begin();
527 it
!= accounts
.end(); ++it
) {
528 OnRefreshTokenAvailable(*it
);
532 void AccountTrackerService::SendRefreshTokenAnnotationRequest(
533 const std::string
& account_id
) {
534 // We only need to send RefreshTokenAnnotationRequest from desktop platforms.
535 #if !defined(OS_ANDROID) && !defined(OS_IOS)
536 if (IsRefreshTokenDeviceIdExperimentEnabled() ||
537 base::CommandLine::ForCurrentProcess()->HasSwitch(
538 switches::kEnableRefreshTokenAnnotationRequest
)) {
539 scoped_ptr
<RefreshTokenAnnotationRequest
> request
=
540 RefreshTokenAnnotationRequest::SendIfNeeded(
541 signin_client_
->GetPrefs(), token_service_
, signin_client_
,
542 signin_client_
->GetURLRequestContext(), account_id
,
544 &AccountTrackerService::RefreshTokenAnnotationRequestDone
,
545 base::Unretained(this), account_id
));
546 // If request was sent AccountTrackerService needs to own request till it
549 refresh_token_annotation_requests_
.set(account_id
, request
.Pass());
554 void AccountTrackerService::RefreshTokenAnnotationRequestDone(
555 const std::string
& account_id
) {
556 refresh_token_annotation_requests_
.erase(account_id
);
559 std::string
AccountTrackerService::PickAccountIdForAccount(
560 const std::string
& gaia
,
561 const std::string
& email
) {
562 return PickAccountIdForAccount(signin_client_
->GetPrefs(), gaia
, email
);
566 std::string
AccountTrackerService::PickAccountIdForAccount(
567 PrefService
* pref_service
,
568 const std::string
& gaia
,
569 const std::string
& email
) {
570 DCHECK(!gaia
.empty());
571 DCHECK(!email
.empty());
572 switch(GetMigrationState(pref_service
)) {
573 case MIGRATION_NOT_STARTED
:
574 case MIGRATION_IN_PROGRESS
:
575 // Some tests don't use a real email address. To support these cases,
576 // don't try to canonicalize these strings.
577 return (email
.find('@') == std::string::npos
) ? email
:
578 gaia::CanonicalizeEmail(email
);
587 void AccountTrackerService::SeedAccountInfo(const std::string
& gaia
,
588 const std::string
& email
) {
589 DVLOG(1) << "AccountTrackerService::SeedAccountInfo"
590 << " gaia_id=" << gaia
591 << " email=" << email
;
593 DCHECK(!gaia
.empty());
594 DCHECK(!email
.empty());
595 const std::string account_id
= PickAccountIdForAccount(gaia
, email
);
596 const bool already_exists
= ContainsKey(accounts_
, account_id
);
597 StartTrackingAccount(account_id
);
598 AccountState
& state
= accounts_
[account_id
];
599 DCHECK(!already_exists
|| state
.info
.gaia
== gaia
);
600 state
.info
.gaia
= gaia
;
601 state
.info
.email
= email
;