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/prefs/scoped_user_pref_update.h"
11 #include "base/profiler/scoped_tracker.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/trace_event/trace_event.h"
15 #include "components/pref_registry/pref_registry_syncable.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"
22 const char kAccountKeyPath
[] = "account_id";
23 const char kAccountEmailPath
[] = "email";
24 const char kAccountGaiaPath
[] = "gaia";
25 const char kAccountHostedDomainPath
[] = "hd";
26 const char kAccountFullNamePath
[] = "full_name";
27 const char kAccountGivenNamePath
[] = "given_name";
28 const char kAccountLocalePath
[] = "locale";
29 const char kAccountPictureURLPath
[] = "picture_url";
30 const char kAccountChildAccountStatusPath
[] = "is_child_account";
32 // TODO(M48): Remove deprecated preference migration.
33 const char kAccountServiceFlagsPath
[] = "service_flags";
35 void RemoveDeprecatedServiceFlags(PrefService
* pref_service
) {
36 ListPrefUpdate
update(pref_service
, AccountTrackerService::kAccountInfoPref
);
37 for (size_t i
= 0; i
< update
->GetSize(); ++i
) {
38 base::DictionaryValue
* dict
= nullptr;
39 if (update
->GetDictionary(i
, &dict
))
40 dict
->RemoveWithoutPathExpansion(kAccountServiceFlagsPath
, nullptr);
46 const char AccountTrackerService::kAccountInfoPref
[] = "account_info";
48 const char AccountTrackerService::kChildAccountServiceFlag
[] = "uca";
50 // This must be a string which can never be a valid domain.
51 const char AccountTrackerService::kNoHostedDomainFound
[] = "NO_HOSTED_DOMAIN";
53 // This must be a string which can never be a valid picture URL.
54 const char AccountTrackerService::kNoPictureURLFound
[] = "NO_PICTURE_URL";
56 AccountTrackerService::AccountTrackerService() : signin_client_(nullptr) {}
58 AccountTrackerService::~AccountTrackerService() {
62 void AccountTrackerService::RegisterPrefs(
63 user_prefs::PrefRegistrySyncable
* registry
) {
64 registry
->RegisterListPref(AccountTrackerService::kAccountInfoPref
);
65 registry
->RegisterIntegerPref(prefs::kAccountIdMigrationState
,
66 AccountTrackerService::MIGRATION_NOT_STARTED
);
69 void AccountTrackerService::Initialize(SigninClient
* signin_client
) {
70 DCHECK(signin_client
);
71 DCHECK(!signin_client_
);
72 signin_client_
= signin_client
;
76 void AccountTrackerService::Shutdown() {
77 signin_client_
= nullptr;
81 void AccountTrackerService::AddObserver(Observer
* observer
) {
82 observer_list_
.AddObserver(observer
);
85 void AccountTrackerService::RemoveObserver(Observer
* observer
) {
86 observer_list_
.RemoveObserver(observer
);
89 std::vector
<AccountInfo
> AccountTrackerService::GetAccounts() const {
90 std::vector
<AccountInfo
> accounts
;
92 for (std::map
<std::string
, AccountState
>::const_iterator it
=
94 it
!= accounts_
.end();
96 const AccountState
& state
= it
->second
;
97 accounts
.push_back(state
.info
);
102 AccountInfo
AccountTrackerService::GetAccountInfo(
103 const std::string
& account_id
) {
104 if (ContainsKey(accounts_
, account_id
))
105 return accounts_
[account_id
].info
;
107 return AccountInfo();
110 AccountInfo
AccountTrackerService::FindAccountInfoByGaiaId(
111 const std::string
& gaia_id
) {
112 if (!gaia_id
.empty()) {
113 for (std::map
<std::string
, AccountState
>::const_iterator it
=
115 it
!= accounts_
.end();
117 const AccountState
& state
= it
->second
;
118 if (state
.info
.gaia
== gaia_id
)
123 return AccountInfo();
126 AccountInfo
AccountTrackerService::FindAccountInfoByEmail(
127 const std::string
& email
) {
128 if (!email
.empty()) {
129 for (std::map
<std::string
, AccountState
>::const_iterator it
=
131 it
!= accounts_
.end();
133 const AccountState
& state
= it
->second
;
134 if (gaia::AreEmailsSame(state
.info
.email
, email
))
139 return AccountInfo();
142 AccountTrackerService::AccountIdMigrationState
143 AccountTrackerService::GetMigrationState() {
144 return GetMigrationState(signin_client_
->GetPrefs());
147 void AccountTrackerService::SetMigrationState(AccountIdMigrationState state
) {
148 signin_client_
->GetPrefs()->SetInteger(prefs::kAccountIdMigrationState
,
152 void AccountTrackerService::SetMigrationDone() {
153 SetMigrationState(MIGRATION_DONE
);
157 AccountTrackerService::AccountIdMigrationState
158 AccountTrackerService::GetMigrationState(PrefService
* pref_service
) {
159 return static_cast<AccountTrackerService::AccountIdMigrationState
>(
160 pref_service
->GetInteger(prefs::kAccountIdMigrationState
));
163 void AccountTrackerService::NotifyAccountUpdated(const AccountState
& state
) {
164 DCHECK(!state
.info
.gaia
.empty());
166 Observer
, observer_list_
, OnAccountUpdated(state
.info
));
169 void AccountTrackerService::NotifyAccountUpdateFailed(
170 const std::string
& account_id
) {
172 Observer
, observer_list_
, OnAccountUpdateFailed(account_id
));
175 void AccountTrackerService::NotifyAccountRemoved(const AccountState
& state
) {
176 DCHECK(!state
.info
.gaia
.empty());
178 Observer
, observer_list_
, OnAccountRemoved(state
.info
));
181 void AccountTrackerService::StartTrackingAccount(
182 const std::string
& account_id
) {
183 if (!ContainsKey(accounts_
, account_id
)) {
184 DVLOG(1) << "StartTracking " << account_id
;
186 state
.info
.account_id
= account_id
;
187 state
.info
.is_child_account
= false;
188 accounts_
.insert(make_pair(account_id
, state
));
191 // If the info is already available on the client, might as well use it.
192 if (signin_client_
->UpdateAccountInfo(&accounts_
[account_id
].info
))
193 SaveToPrefs(accounts_
[account_id
]);
196 void AccountTrackerService::StopTrackingAccount(const std::string
& account_id
) {
197 DVLOG(1) << "StopTracking " << account_id
;
198 if (ContainsKey(accounts_
, account_id
)) {
199 AccountState
& state
= accounts_
[account_id
];
200 RemoveFromPrefs(state
);
201 if (!state
.info
.gaia
.empty())
202 NotifyAccountRemoved(state
);
204 accounts_
.erase(account_id
);
208 void AccountTrackerService::SetAccountStateFromUserInfo(
209 const std::string
& account_id
,
210 const base::DictionaryValue
* user_info
) {
211 DCHECK(ContainsKey(accounts_
, account_id
));
212 AccountState
& state
= accounts_
[account_id
];
216 if (user_info
->GetString("id", &gaia_id
) &&
217 user_info
->GetString("email", &email
)) {
218 state
.info
.gaia
= gaia_id
;
219 state
.info
.email
= email
;
221 std::string hosted_domain
;
222 if (user_info
->GetString("hd", &hosted_domain
) && !hosted_domain
.empty()) {
223 state
.info
.hosted_domain
= hosted_domain
;
225 state
.info
.hosted_domain
= kNoHostedDomainFound
;
228 user_info
->GetString("name", &state
.info
.full_name
);
229 user_info
->GetString("given_name", &state
.info
.given_name
);
230 user_info
->GetString("locale", &state
.info
.locale
);
232 std::string picture_url
;
233 if(user_info
->GetString("picture", &picture_url
)) {
234 state
.info
.picture_url
= picture_url
;
236 state
.info
.picture_url
= kNoPictureURLFound
;
239 if (state
.info
.IsValid())
240 NotifyAccountUpdated(state
);
244 void AccountTrackerService::SetIsChildAccount(const std::string
& account_id
,
245 const bool& is_child_account
) {
246 DCHECK(ContainsKey(accounts_
, account_id
));
247 AccountState
& state
= accounts_
[account_id
];
248 if (state
.info
.is_child_account
== is_child_account
)
250 state
.info
.is_child_account
= is_child_account
;
251 if (state
.info
.IsValid())
252 NotifyAccountUpdated(state
);
256 bool AccountTrackerService::IsMigratable() {
257 #if !defined(OS_CHROMEOS)
258 for (std::map
<std::string
, AccountState
>::const_iterator it
=
260 it
!= accounts_
.end(); ++it
) {
261 const AccountState
& state
= it
->second
;
262 if ((it
->first
).empty() || state
.info
.gaia
.empty())
271 void AccountTrackerService::MigrateToGaiaId() {
272 std::set
<std::string
> to_remove
;
273 std::map
<std::string
, AccountState
> migrated_accounts
;
274 for (std::map
<std::string
, AccountState
>::const_iterator it
=
276 it
!= accounts_
.end(); ++it
) {
277 const AccountState
& state
= it
->second
;
278 std::string account_id
= it
->first
;
279 if (account_id
!= state
.info
.gaia
) {
280 std::string new_account_id
= state
.info
.gaia
;
281 if (!ContainsKey(accounts_
, new_account_id
)) {
282 AccountState new_state
= state
;
283 new_state
.info
.account_id
= new_account_id
;
284 migrated_accounts
.insert(make_pair(new_account_id
, new_state
));
285 SaveToPrefs(new_state
);
287 to_remove
.insert(account_id
);
291 // Remove any obsolete account.
292 for (auto account_id
: to_remove
) {
293 if (ContainsKey(accounts_
, account_id
)) {
294 AccountState
& state
= accounts_
[account_id
];
295 RemoveFromPrefs(state
);
296 accounts_
.erase(account_id
);
300 for (std::map
<std::string
, AccountState
>::const_iterator it
=
301 migrated_accounts
.begin();
302 it
!= migrated_accounts
.end(); ++it
) {
303 accounts_
.insert(*it
);
307 void AccountTrackerService::LoadFromPrefs() {
308 const base::ListValue
* list
=
309 signin_client_
->GetPrefs()->GetList(kAccountInfoPref
);
310 std::set
<std::string
> to_remove
;
311 bool contains_deprecated_service_flags
= false;
312 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
313 const base::DictionaryValue
* dict
;
314 if (list
->GetDictionary(i
, &dict
)) {
315 base::string16 value
;
316 if (dict
->GetString(kAccountKeyPath
, &value
)) {
317 std::string account_id
= base::UTF16ToUTF8(value
);
319 // Ignore incorrectly persisted non-canonical account ids.
320 if (account_id
.find('@') != std::string::npos
&&
321 account_id
!= gaia::CanonicalizeEmail(account_id
)) {
322 to_remove
.insert(account_id
);
326 StartTrackingAccount(account_id
);
327 AccountState
& state
= accounts_
[account_id
];
329 if (dict
->GetString(kAccountGaiaPath
, &value
))
330 state
.info
.gaia
= base::UTF16ToUTF8(value
);
331 if (dict
->GetString(kAccountEmailPath
, &value
))
332 state
.info
.email
= base::UTF16ToUTF8(value
);
333 if (dict
->GetString(kAccountHostedDomainPath
, &value
))
334 state
.info
.hosted_domain
= base::UTF16ToUTF8(value
);
335 if (dict
->GetString(kAccountFullNamePath
, &value
))
336 state
.info
.full_name
= base::UTF16ToUTF8(value
);
337 if (dict
->GetString(kAccountGivenNamePath
, &value
))
338 state
.info
.given_name
= base::UTF16ToUTF8(value
);
339 if (dict
->GetString(kAccountLocalePath
, &value
))
340 state
.info
.locale
= base::UTF16ToUTF8(value
);
341 if (dict
->GetString(kAccountPictureURLPath
, &value
))
342 state
.info
.picture_url
= base::UTF16ToUTF8(value
);
344 bool is_child_account
= false;
345 // Migrate deprecated service flag preference.
346 const base::ListValue
* service_flags_list
;
347 if (dict
->GetList(kAccountServiceFlagsPath
, &service_flags_list
)) {
348 contains_deprecated_service_flags
= true;
349 std::string flag_string
;
350 for (base::Value
* flag
: *service_flags_list
) {
351 if (flag
->GetAsString(&flag_string
) &&
352 flag_string
== kChildAccountServiceFlag
) {
353 is_child_account
= true;
357 state
.info
.is_child_account
= is_child_account
;
359 if (dict
->GetBoolean(kAccountChildAccountStatusPath
, &is_child_account
))
360 state
.info
.is_child_account
= is_child_account
;
362 if (state
.info
.IsValid())
363 NotifyAccountUpdated(state
);
368 if (contains_deprecated_service_flags
)
369 RemoveDeprecatedServiceFlags(signin_client_
->GetPrefs());
371 // Remove any obsolete prefs.
372 for (auto account_id
: to_remove
) {
374 state
.info
.account_id
= account_id
;
375 RemoveFromPrefs(state
);
378 if (GetMigrationState() != MIGRATION_DONE
) {
379 if (IsMigratable()) {
380 if (accounts_
.empty()) {
383 SetMigrationState(MIGRATION_IN_PROGRESS
);
390 void AccountTrackerService::SaveToPrefs(const AccountState
& state
) {
391 if (!signin_client_
->GetPrefs())
394 base::DictionaryValue
* dict
= nullptr;
395 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
396 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
397 for (size_t i
= 0; i
< update
->GetSize(); ++i
, dict
= nullptr) {
398 if (update
->GetDictionary(i
, &dict
)) {
399 base::string16 value
;
400 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
)
406 dict
= new base::DictionaryValue();
407 update
->Append(dict
); // |update| takes ownership.
408 dict
->SetString(kAccountKeyPath
, account_id_16
);
411 dict
->SetString(kAccountEmailPath
, state
.info
.email
);
412 dict
->SetString(kAccountGaiaPath
, state
.info
.gaia
);
413 dict
->SetString(kAccountHostedDomainPath
, state
.info
.hosted_domain
);
414 dict
->SetString(kAccountFullNamePath
, state
.info
.full_name
);
415 dict
->SetString(kAccountGivenNamePath
, state
.info
.given_name
);
416 dict
->SetString(kAccountLocalePath
, state
.info
.locale
);
417 dict
->SetString(kAccountPictureURLPath
, state
.info
.picture_url
);
418 dict
->SetBoolean(kAccountChildAccountStatusPath
, state
.info
.is_child_account
);
421 void AccountTrackerService::RemoveFromPrefs(const AccountState
& state
) {
422 if (!signin_client_
->GetPrefs())
425 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
426 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
427 for(size_t i
= 0; i
< update
->GetSize(); ++i
) {
428 base::DictionaryValue
* dict
= nullptr;
429 if (update
->GetDictionary(i
, &dict
)) {
430 base::string16 value
;
431 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
) {
432 update
->Remove(i
, nullptr);
439 std::string
AccountTrackerService::PickAccountIdForAccount(
440 const std::string
& gaia
,
441 const std::string
& email
) {
442 return PickAccountIdForAccount(signin_client_
->GetPrefs(), gaia
, email
);
446 std::string
AccountTrackerService::PickAccountIdForAccount(
447 PrefService
* pref_service
,
448 const std::string
& gaia
,
449 const std::string
& email
) {
450 DCHECK(!gaia
.empty() ||
451 GetMigrationState(pref_service
) == MIGRATION_NOT_STARTED
);
452 DCHECK(!email
.empty());
453 switch(GetMigrationState(pref_service
)) {
454 case MIGRATION_NOT_STARTED
:
455 // Some tests don't use a real email address. To support these cases,
456 // don't try to canonicalize these strings.
457 return (email
.find('@') == std::string::npos
) ? email
:
458 gaia::CanonicalizeEmail(email
);
459 case MIGRATION_IN_PROGRESS
:
468 std::string
AccountTrackerService::SeedAccountInfo(const std::string
& gaia
,
469 const std::string
& email
) {
470 const std::string account_id
= PickAccountIdForAccount(gaia
, email
);
471 const bool already_exists
= ContainsKey(accounts_
, account_id
);
472 StartTrackingAccount(account_id
);
473 AccountState
& state
= accounts_
[account_id
];
474 DCHECK(!already_exists
|| state
.info
.gaia
.empty() || state
.info
.gaia
== gaia
);
475 state
.info
.gaia
= gaia
;
476 state
.info
.email
= email
;
479 DVLOG(1) << "AccountTrackerService::SeedAccountInfo"
480 << " account_id=" << account_id
481 << " gaia_id=" << gaia
482 << " email=" << email
;
487 void AccountTrackerService::SeedAccountInfo(AccountInfo info
) {
488 info
.account_id
= PickAccountIdForAccount(info
.gaia
, info
.email
);
489 if (info
.hosted_domain
.empty()) {
490 info
.hosted_domain
= kNoHostedDomainFound
;
494 if(!ContainsKey(accounts_
, info
.account_id
)) {
495 SeedAccountInfo(info
.gaia
, info
.email
);
498 AccountState
& state
= accounts_
[info
.account_id
];
500 NotifyAccountUpdated(state
);
505 void AccountTrackerService::RemoveAccount(const std::string
& account_id
) {
506 StopTrackingAccount(account_id
);