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::AccountInfo::AccountInfo() {}
57 AccountTrackerService::AccountInfo::~AccountInfo() {}
59 bool AccountTrackerService::AccountInfo::IsValid() const {
60 return !account_id
.empty() && !email
.empty() && !gaia
.empty() &&
61 !hosted_domain
.empty() && !full_name
.empty() && !given_name
.empty() &&
62 !locale
.empty() && !picture_url
.empty();
65 AccountTrackerService::AccountTrackerService() : signin_client_(nullptr) {}
67 AccountTrackerService::~AccountTrackerService() {
71 void AccountTrackerService::RegisterPrefs(
72 user_prefs::PrefRegistrySyncable
* registry
) {
73 registry
->RegisterListPref(AccountTrackerService::kAccountInfoPref
);
74 registry
->RegisterIntegerPref(prefs::kAccountIdMigrationState
,
75 AccountTrackerService::MIGRATION_NOT_STARTED
);
78 void AccountTrackerService::Initialize(SigninClient
* signin_client
) {
79 DCHECK(signin_client
);
80 DCHECK(!signin_client_
);
81 signin_client_
= signin_client
;
85 void AccountTrackerService::Shutdown() {
86 signin_client_
= nullptr;
90 void AccountTrackerService::AddObserver(Observer
* observer
) {
91 observer_list_
.AddObserver(observer
);
94 void AccountTrackerService::RemoveObserver(Observer
* observer
) {
95 observer_list_
.RemoveObserver(observer
);
98 std::vector
<AccountTrackerService::AccountInfo
>
99 AccountTrackerService::GetAccounts() const {
100 std::vector
<AccountInfo
> accounts
;
102 for (std::map
<std::string
, AccountState
>::const_iterator it
=
104 it
!= accounts_
.end();
106 const AccountState
& state
= it
->second
;
107 accounts
.push_back(state
.info
);
112 AccountTrackerService::AccountInfo
AccountTrackerService::GetAccountInfo(
113 const std::string
& account_id
) {
114 if (ContainsKey(accounts_
, account_id
))
115 return accounts_
[account_id
].info
;
117 return AccountInfo();
120 AccountTrackerService::AccountInfo
121 AccountTrackerService::FindAccountInfoByGaiaId(
122 const std::string
& gaia_id
) {
123 if (!gaia_id
.empty()) {
124 for (std::map
<std::string
, AccountState
>::const_iterator it
=
126 it
!= accounts_
.end();
128 const AccountState
& state
= it
->second
;
129 if (state
.info
.gaia
== gaia_id
)
134 return AccountInfo();
137 AccountTrackerService::AccountInfo
138 AccountTrackerService::FindAccountInfoByEmail(
139 const std::string
& email
) {
140 if (!email
.empty()) {
141 for (std::map
<std::string
, AccountState
>::const_iterator it
=
143 it
!= accounts_
.end();
145 const AccountState
& state
= it
->second
;
146 if (gaia::AreEmailsSame(state
.info
.email
, email
))
151 return AccountInfo();
154 AccountTrackerService::AccountIdMigrationState
155 AccountTrackerService::GetMigrationState() {
156 return GetMigrationState(signin_client_
->GetPrefs());
159 void AccountTrackerService::SetMigrationState(AccountIdMigrationState state
) {
160 signin_client_
->GetPrefs()->SetInteger(prefs::kAccountIdMigrationState
,
164 void AccountTrackerService::SetMigrationDone() {
165 SetMigrationState(MIGRATION_DONE
);
169 AccountTrackerService::AccountIdMigrationState
170 AccountTrackerService::GetMigrationState(PrefService
* pref_service
) {
171 return static_cast<AccountTrackerService::AccountIdMigrationState
>(
172 pref_service
->GetInteger(prefs::kAccountIdMigrationState
));
175 void AccountTrackerService::NotifyAccountUpdated(const AccountState
& state
) {
176 DCHECK(!state
.info
.gaia
.empty());
178 Observer
, observer_list_
, OnAccountUpdated(state
.info
));
181 void AccountTrackerService::NotifyAccountUpdateFailed(
182 const std::string
& account_id
) {
184 Observer
, observer_list_
, OnAccountUpdateFailed(account_id
));
187 void AccountTrackerService::NotifyAccountRemoved(const AccountState
& state
) {
188 DCHECK(!state
.info
.gaia
.empty());
190 Observer
, observer_list_
, OnAccountRemoved(state
.info
));
193 void AccountTrackerService::StartTrackingAccount(
194 const std::string
& account_id
) {
195 if (!ContainsKey(accounts_
, account_id
)) {
196 DVLOG(1) << "StartTracking " << account_id
;
198 state
.info
.account_id
= account_id
;
199 state
.info
.is_child_account
= false;
200 accounts_
.insert(make_pair(account_id
, state
));
203 // If the info is already available on the client, might as well use it.
204 if (signin_client_
->UpdateAccountInfo(&accounts_
[account_id
].info
))
205 SaveToPrefs(accounts_
[account_id
]);
208 void AccountTrackerService::StopTrackingAccount(const std::string
& account_id
) {
209 DVLOG(1) << "StopTracking " << account_id
;
210 if (ContainsKey(accounts_
, account_id
)) {
211 AccountState
& state
= accounts_
[account_id
];
212 RemoveFromPrefs(state
);
213 if (!state
.info
.gaia
.empty())
214 NotifyAccountRemoved(state
);
216 accounts_
.erase(account_id
);
220 void AccountTrackerService::SetAccountStateFromUserInfo(
221 const std::string
& account_id
,
222 const base::DictionaryValue
* user_info
) {
223 DCHECK(ContainsKey(accounts_
, account_id
));
224 AccountState
& state
= accounts_
[account_id
];
228 if (user_info
->GetString("id", &gaia_id
) &&
229 user_info
->GetString("email", &email
)) {
230 state
.info
.gaia
= gaia_id
;
231 state
.info
.email
= email
;
233 std::string hosted_domain
;
234 if (user_info
->GetString("hd", &hosted_domain
) && !hosted_domain
.empty()) {
235 state
.info
.hosted_domain
= hosted_domain
;
237 state
.info
.hosted_domain
= kNoHostedDomainFound
;
240 user_info
->GetString("name", &state
.info
.full_name
);
241 user_info
->GetString("given_name", &state
.info
.given_name
);
242 user_info
->GetString("locale", &state
.info
.locale
);
244 std::string picture_url
;
245 if(user_info
->GetString("picture", &picture_url
)) {
246 state
.info
.picture_url
= picture_url
;
248 state
.info
.picture_url
= kNoPictureURLFound
;
251 if (state
.info
.IsValid())
252 NotifyAccountUpdated(state
);
256 void AccountTrackerService::SetIsChildAccount(const std::string
& account_id
,
257 const bool& is_child_account
) {
258 DCHECK(ContainsKey(accounts_
, account_id
));
259 AccountState
& state
= accounts_
[account_id
];
260 if (state
.info
.is_child_account
== is_child_account
)
262 state
.info
.is_child_account
= is_child_account
;
263 if (state
.info
.IsValid())
264 NotifyAccountUpdated(state
);
268 bool AccountTrackerService::IsMigratable() {
269 #if !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
270 for (std::map
<std::string
, AccountState
>::const_iterator it
=
272 it
!= accounts_
.end(); ++it
) {
273 const AccountState
& state
= it
->second
;
274 if ((it
->first
).empty() || state
.info
.gaia
.empty())
283 void AccountTrackerService::MigrateToGaiaId() {
284 std::set
<std::string
> to_remove
;
285 std::map
<std::string
, AccountState
> migrated_accounts
;
286 for (std::map
<std::string
, AccountState
>::const_iterator it
=
288 it
!= accounts_
.end(); ++it
) {
289 const AccountState
& state
= it
->second
;
290 std::string account_id
= it
->first
;
291 if (account_id
!= state
.info
.gaia
) {
292 std::string new_account_id
= state
.info
.gaia
;
293 if (!ContainsKey(accounts_
, new_account_id
)) {
294 AccountState new_state
= state
;
295 new_state
.info
.account_id
= new_account_id
;
296 migrated_accounts
.insert(make_pair(new_account_id
, new_state
));
297 SaveToPrefs(new_state
);
299 to_remove
.insert(account_id
);
303 // Remove any obsolete account.
304 for (auto account_id
: to_remove
) {
305 if (ContainsKey(accounts_
, account_id
)) {
306 AccountState
& state
= accounts_
[account_id
];
307 RemoveFromPrefs(state
);
308 accounts_
.erase(account_id
);
312 for (std::map
<std::string
, AccountState
>::const_iterator it
=
313 migrated_accounts
.begin();
314 it
!= migrated_accounts
.end(); ++it
) {
315 accounts_
.insert(*it
);
319 void AccountTrackerService::LoadFromPrefs() {
320 const base::ListValue
* list
=
321 signin_client_
->GetPrefs()->GetList(kAccountInfoPref
);
322 std::set
<std::string
> to_remove
;
323 bool contains_deprecated_service_flags
= false;
324 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
325 const base::DictionaryValue
* dict
;
326 if (list
->GetDictionary(i
, &dict
)) {
327 base::string16 value
;
328 if (dict
->GetString(kAccountKeyPath
, &value
)) {
329 std::string account_id
= base::UTF16ToUTF8(value
);
331 // Ignore incorrectly persisted non-canonical account ids.
332 if (account_id
.find('@') != std::string::npos
&&
333 account_id
!= gaia::CanonicalizeEmail(account_id
)) {
334 to_remove
.insert(account_id
);
338 StartTrackingAccount(account_id
);
339 AccountState
& state
= accounts_
[account_id
];
341 if (dict
->GetString(kAccountGaiaPath
, &value
))
342 state
.info
.gaia
= base::UTF16ToUTF8(value
);
343 if (dict
->GetString(kAccountEmailPath
, &value
))
344 state
.info
.email
= base::UTF16ToUTF8(value
);
345 if (dict
->GetString(kAccountHostedDomainPath
, &value
))
346 state
.info
.hosted_domain
= base::UTF16ToUTF8(value
);
347 if (dict
->GetString(kAccountFullNamePath
, &value
))
348 state
.info
.full_name
= base::UTF16ToUTF8(value
);
349 if (dict
->GetString(kAccountGivenNamePath
, &value
))
350 state
.info
.given_name
= base::UTF16ToUTF8(value
);
351 if (dict
->GetString(kAccountLocalePath
, &value
))
352 state
.info
.locale
= base::UTF16ToUTF8(value
);
353 if (dict
->GetString(kAccountPictureURLPath
, &value
))
354 state
.info
.picture_url
= base::UTF16ToUTF8(value
);
356 bool is_child_account
= false;
357 // Migrate deprecated service flag preference.
358 const base::ListValue
* service_flags_list
;
359 if (dict
->GetList(kAccountServiceFlagsPath
, &service_flags_list
)) {
360 contains_deprecated_service_flags
= true;
361 std::string flag_string
;
362 for (base::Value
* flag
: *service_flags_list
) {
363 if (flag
->GetAsString(&flag_string
) &&
364 flag_string
== kChildAccountServiceFlag
) {
365 is_child_account
= true;
369 state
.info
.is_child_account
= is_child_account
;
371 if (dict
->GetBoolean(kAccountChildAccountStatusPath
, &is_child_account
))
372 state
.info
.is_child_account
= is_child_account
;
374 if (state
.info
.IsValid())
375 NotifyAccountUpdated(state
);
380 if (contains_deprecated_service_flags
)
381 RemoveDeprecatedServiceFlags(signin_client_
->GetPrefs());
383 // Remove any obsolete prefs.
384 for (auto account_id
: to_remove
) {
386 state
.info
.account_id
= account_id
;
387 RemoveFromPrefs(state
);
390 if (GetMigrationState() != MIGRATION_DONE
) {
391 if (IsMigratable()) {
392 if (accounts_
.empty()) {
395 SetMigrationState(MIGRATION_IN_PROGRESS
);
402 void AccountTrackerService::SaveToPrefs(const AccountState
& state
) {
403 if (!signin_client_
->GetPrefs())
406 base::DictionaryValue
* dict
= nullptr;
407 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
408 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
409 for (size_t i
= 0; i
< update
->GetSize(); ++i
, dict
= nullptr) {
410 if (update
->GetDictionary(i
, &dict
)) {
411 base::string16 value
;
412 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
)
418 dict
= new base::DictionaryValue();
419 update
->Append(dict
); // |update| takes ownership.
420 dict
->SetString(kAccountKeyPath
, account_id_16
);
423 dict
->SetString(kAccountEmailPath
, state
.info
.email
);
424 dict
->SetString(kAccountGaiaPath
, state
.info
.gaia
);
425 dict
->SetString(kAccountHostedDomainPath
, state
.info
.hosted_domain
);
426 dict
->SetString(kAccountFullNamePath
, state
.info
.full_name
);
427 dict
->SetString(kAccountGivenNamePath
, state
.info
.given_name
);
428 dict
->SetString(kAccountLocalePath
, state
.info
.locale
);
429 dict
->SetString(kAccountPictureURLPath
, state
.info
.picture_url
);
430 dict
->SetBoolean(kAccountChildAccountStatusPath
, state
.info
.is_child_account
);
433 void AccountTrackerService::RemoveFromPrefs(const AccountState
& state
) {
434 if (!signin_client_
->GetPrefs())
437 base::string16 account_id_16
= base::UTF8ToUTF16(state
.info
.account_id
);
438 ListPrefUpdate
update(signin_client_
->GetPrefs(), kAccountInfoPref
);
439 for(size_t i
= 0; i
< update
->GetSize(); ++i
) {
440 base::DictionaryValue
* dict
= nullptr;
441 if (update
->GetDictionary(i
, &dict
)) {
442 base::string16 value
;
443 if (dict
->GetString(kAccountKeyPath
, &value
) && value
== account_id_16
) {
444 update
->Remove(i
, nullptr);
451 std::string
AccountTrackerService::PickAccountIdForAccount(
452 const std::string
& gaia
,
453 const std::string
& email
) {
454 return PickAccountIdForAccount(signin_client_
->GetPrefs(), gaia
, email
);
458 std::string
AccountTrackerService::PickAccountIdForAccount(
459 PrefService
* pref_service
,
460 const std::string
& gaia
,
461 const std::string
& email
) {
462 DCHECK(!gaia
.empty() ||
463 GetMigrationState(pref_service
) == MIGRATION_NOT_STARTED
);
464 DCHECK(!email
.empty());
465 switch(GetMigrationState(pref_service
)) {
466 case MIGRATION_NOT_STARTED
:
467 // Some tests don't use a real email address. To support these cases,
468 // don't try to canonicalize these strings.
469 return (email
.find('@') == std::string::npos
) ? email
:
470 gaia::CanonicalizeEmail(email
);
471 case MIGRATION_IN_PROGRESS
:
480 std::string
AccountTrackerService::SeedAccountInfo(const std::string
& gaia
,
481 const std::string
& email
) {
482 const std::string account_id
= PickAccountIdForAccount(gaia
, email
);
483 const bool already_exists
= ContainsKey(accounts_
, account_id
);
484 StartTrackingAccount(account_id
);
485 AccountState
& state
= accounts_
[account_id
];
486 DCHECK(!already_exists
|| state
.info
.gaia
.empty() || state
.info
.gaia
== gaia
);
487 state
.info
.gaia
= gaia
;
488 state
.info
.email
= email
;
491 DVLOG(1) << "AccountTrackerService::SeedAccountInfo"
492 << " account_id=" << account_id
493 << " gaia_id=" << gaia
494 << " email=" << email
;
499 void AccountTrackerService::SeedAccountInfo(
500 AccountTrackerService::AccountInfo info
) {
501 info
.account_id
= PickAccountIdForAccount(info
.gaia
, info
.email
);
502 if (info
.hosted_domain
.empty()) {
503 info
.hosted_domain
= kNoHostedDomainFound
;
507 if(!ContainsKey(accounts_
, info
.account_id
)) {
508 SeedAccountInfo(info
.gaia
, info
.email
);
511 AccountState
& state
= accounts_
[info
.account_id
];
513 NotifyAccountUpdated(state
);