Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / signin / core / browser / account_tracker_service.cc
blob59250336be5f8e3441a95696233a8784d760b846
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"
20 namespace {
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);
44 } // namespace
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() {
61 // static
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;
73 LoadFromPrefs();
76 void AccountTrackerService::Shutdown() {
77 signin_client_ = nullptr;
78 accounts_.clear();
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 =
93 accounts_.begin();
94 it != accounts_.end();
95 ++it) {
96 const AccountState& state = it->second;
97 accounts.push_back(state.info);
99 return accounts;
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 =
114 accounts_.begin();
115 it != accounts_.end();
116 ++it) {
117 const AccountState& state = it->second;
118 if (state.info.gaia == gaia_id)
119 return state.info;
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 =
130 accounts_.begin();
131 it != accounts_.end();
132 ++it) {
133 const AccountState& state = it->second;
134 if (gaia::AreEmailsSame(state.info.email, email))
135 return state.info;
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,
149 state);
152 void AccountTrackerService::SetMigrationDone() {
153 SetMigrationState(MIGRATION_DONE);
156 // static
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());
165 FOR_EACH_OBSERVER(
166 Observer, observer_list_, OnAccountUpdated(state.info));
169 void AccountTrackerService::NotifyAccountUpdateFailed(
170 const std::string& account_id) {
171 FOR_EACH_OBSERVER(
172 Observer, observer_list_, OnAccountUpdateFailed(account_id));
175 void AccountTrackerService::NotifyAccountRemoved(const AccountState& state) {
176 DCHECK(!state.info.gaia.empty());
177 FOR_EACH_OBSERVER(
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;
185 AccountState state;
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];
214 std::string gaia_id;
215 std::string email;
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;
224 } else {
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;
235 } else {
236 state.info.picture_url = kNoPictureURLFound;
239 if (state.info.IsValid())
240 NotifyAccountUpdated(state);
241 SaveToPrefs(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)
249 return;
250 state.info.is_child_account = is_child_account;
251 if (state.info.IsValid())
252 NotifyAccountUpdated(state);
253 SaveToPrefs(state);
256 bool AccountTrackerService::IsMigratable() {
257 #if !defined(OS_CHROMEOS)
258 for (std::map<std::string, AccountState>::const_iterator it =
259 accounts_.begin();
260 it != accounts_.end(); ++it) {
261 const AccountState& state = it->second;
262 if ((it->first).empty() || state.info.gaia.empty())
263 return false;
265 return true;
266 #else
267 return false;
268 #endif
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 =
275 accounts_.begin();
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);
323 continue;
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;
354 break;
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) {
373 AccountState state;
374 state.info.account_id = account_id;
375 RemoveFromPrefs(state);
378 if (GetMigrationState() != MIGRATION_DONE) {
379 if (IsMigratable()) {
380 if (accounts_.empty()) {
381 SetMigrationDone();
382 } else {
383 SetMigrationState(MIGRATION_IN_PROGRESS);
384 MigrateToGaiaId();
390 void AccountTrackerService::SaveToPrefs(const AccountState& state) {
391 if (!signin_client_->GetPrefs())
392 return;
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)
401 break;
405 if (!dict) {
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())
423 return;
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);
433 break;
439 std::string AccountTrackerService::PickAccountIdForAccount(
440 const std::string& gaia,
441 const std::string& email) {
442 return PickAccountIdForAccount(signin_client_->GetPrefs(), gaia, email);
445 // static
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:
460 case MIGRATION_DONE:
461 return gaia;
462 default:
463 NOTREACHED();
464 return email;
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;
477 SaveToPrefs(state);
479 DVLOG(1) << "AccountTrackerService::SeedAccountInfo"
480 << " account_id=" << account_id
481 << " gaia_id=" << gaia
482 << " email=" << email;
484 return account_id;
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;
493 if(info.IsValid()) {
494 if(!ContainsKey(accounts_, info.account_id)) {
495 SeedAccountInfo(info.gaia, info.email);
498 AccountState& state = accounts_[info.account_id];
499 state.info = info;
500 NotifyAccountUpdated(state);
501 SaveToPrefs(state);
505 void AccountTrackerService::RemoveAccount(const std::string& account_id) {
506 StopTrackingAccount(account_id);