Stack sampling profiler: add fire-and-forget interface
[chromium-blink-merge.git] / components / signin / core / browser / account_tracker_service.cc
blob6e36fc631b7d6b7ad4017dd86fe79a1646ec8c8f
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::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() {
70 // static
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;
82 LoadFromPrefs();
85 void AccountTrackerService::Shutdown() {
86 signin_client_ = nullptr;
87 accounts_.clear();
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 =
103 accounts_.begin();
104 it != accounts_.end();
105 ++it) {
106 const AccountState& state = it->second;
107 accounts.push_back(state.info);
109 return accounts;
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 =
125 accounts_.begin();
126 it != accounts_.end();
127 ++it) {
128 const AccountState& state = it->second;
129 if (state.info.gaia == gaia_id)
130 return state.info;
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 =
142 accounts_.begin();
143 it != accounts_.end();
144 ++it) {
145 const AccountState& state = it->second;
146 if (gaia::AreEmailsSame(state.info.email, email))
147 return state.info;
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,
161 state);
164 void AccountTrackerService::SetMigrationDone() {
165 SetMigrationState(MIGRATION_DONE);
168 // static
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());
177 FOR_EACH_OBSERVER(
178 Observer, observer_list_, OnAccountUpdated(state.info));
181 void AccountTrackerService::NotifyAccountUpdateFailed(
182 const std::string& account_id) {
183 FOR_EACH_OBSERVER(
184 Observer, observer_list_, OnAccountUpdateFailed(account_id));
187 void AccountTrackerService::NotifyAccountRemoved(const AccountState& state) {
188 DCHECK(!state.info.gaia.empty());
189 FOR_EACH_OBSERVER(
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;
197 AccountState state;
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];
226 std::string gaia_id;
227 std::string email;
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;
236 } else {
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;
247 } else {
248 state.info.picture_url = kNoPictureURLFound;
251 if (state.info.IsValid())
252 NotifyAccountUpdated(state);
253 SaveToPrefs(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)
261 return;
262 state.info.is_child_account = is_child_account;
263 if (state.info.IsValid())
264 NotifyAccountUpdated(state);
265 SaveToPrefs(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 =
271 accounts_.begin();
272 it != accounts_.end(); ++it) {
273 const AccountState& state = it->second;
274 if ((it->first).empty() || state.info.gaia.empty())
275 return false;
277 return true;
278 #else
279 return false;
280 #endif
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 =
287 accounts_.begin();
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);
335 continue;
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;
366 break;
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) {
385 AccountState state;
386 state.info.account_id = account_id;
387 RemoveFromPrefs(state);
390 if (GetMigrationState() != MIGRATION_DONE) {
391 if (IsMigratable()) {
392 if (accounts_.empty()) {
393 SetMigrationDone();
394 } else {
395 SetMigrationState(MIGRATION_IN_PROGRESS);
396 MigrateToGaiaId();
402 void AccountTrackerService::SaveToPrefs(const AccountState& state) {
403 if (!signin_client_->GetPrefs())
404 return;
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)
413 break;
417 if (!dict) {
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())
435 return;
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);
445 break;
451 std::string AccountTrackerService::PickAccountIdForAccount(
452 const std::string& gaia,
453 const std::string& email) {
454 return PickAccountIdForAccount(signin_client_->GetPrefs(), gaia, email);
457 // static
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:
472 case MIGRATION_DONE:
473 return gaia;
474 default:
475 NOTREACHED();
476 return email;
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;
489 SaveToPrefs(state);
491 DVLOG(1) << "AccountTrackerService::SeedAccountInfo"
492 << " account_id=" << account_id
493 << " gaia_id=" << gaia
494 << " email=" << email;
496 return account_id;
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;
506 if(info.IsValid()) {
507 if(!ContainsKey(accounts_, info.account_id)) {
508 SeedAccountInfo(info.gaia, info.email);
511 AccountState& state = accounts_[info.account_id];
512 state.info = info;
513 NotifyAccountUpdated(state);
514 SaveToPrefs(state);