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_reconcilor.h"
10 #include "base/json/json_reader.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/time/time.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_client.h"
18 #include "components/signin/core/browser/signin_metrics.h"
19 #include "components/signin/core/browser/signin_oauth_helper.h"
20 #include "components/signin/core/common/profile_management_switches.h"
21 #include "google_apis/gaia/gaia_auth_fetcher.h"
22 #include "google_apis/gaia/gaia_auth_util.h"
23 #include "google_apis/gaia/gaia_constants.h"
24 #include "google_apis/gaia/gaia_oauth_client.h"
25 #include "google_apis/gaia/gaia_urls.h"
26 #include "net/cookies/canonical_cookie.h"
31 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
33 bool operator()(const std::pair<std::string, bool>& p1,
34 const std::pair<std::string, bool>& p2) const;
37 bool EmailEqualToFunc::operator()(
38 const std::pair<std::string, bool>& p1,
39 const std::pair<std::string, bool>& p2) const {
40 return p1.second == p2.second && gaia::AreEmailsSame(p1.first, p2.first);
46 // Fetches a refresh token from the given session in the GAIA cookie. This is
47 // a best effort only. If it should fail, another reconcile action will occur
49 class AccountReconcilor::RefreshTokenFetcher
50 : public SigninOAuthHelper,
51 public SigninOAuthHelper::Consumer {
53 RefreshTokenFetcher(AccountReconcilor* reconcilor,
54 const std::string& account_id,
56 const std::string& signin_scoped_device_id);
57 virtual ~RefreshTokenFetcher() {}
60 // Overridden from GaiaAuthConsumer:
61 virtual void OnSigninOAuthInformationAvailable(
62 const std::string& email,
63 const std::string& display_email,
64 const std::string& refresh_token) OVERRIDE;
66 // Called when an error occurs while getting the information.
67 virtual void OnSigninOAuthInformationFailure(
68 const GoogleServiceAuthError& error) OVERRIDE;
70 AccountReconcilor* reconcilor_;
71 const std::string account_id_;
74 DISALLOW_COPY_AND_ASSIGN(RefreshTokenFetcher);
77 AccountReconcilor::RefreshTokenFetcher::RefreshTokenFetcher(
78 AccountReconcilor* reconcilor,
79 const std::string& account_id,
81 const std::string& signin_scoped_device_id)
82 : SigninOAuthHelper(reconcilor->client()->GetURLRequestContext(),
83 base::IntToString(session_index),
84 signin_scoped_device_id,
86 reconcilor_(reconcilor),
87 account_id_(account_id),
88 session_index_(session_index) {
90 DCHECK(!account_id.empty());
93 void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationAvailable(
94 const std::string& email,
95 const std::string& display_email,
96 const std::string& refresh_token) {
97 VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationAvailable:"
98 << " account=" << account_id_ << " email=" << email
99 << " displayEmail=" << display_email;
101 // TODO(rogerta): because of the problem with email vs displayEmail and
102 // emails that have been canonicalized, the argument |email| is used here
103 // to make sure the correct string is used when calling the token service.
104 // This will be cleaned up when chrome moves to using gaia obfuscated id.
105 reconcilor_->HandleRefreshTokenFetched(email, refresh_token);
108 void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationFailure(
109 const GoogleServiceAuthError& error) {
110 VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationFailure:"
111 << " account=" << account_id_ << " session_index=" << session_index_;
112 reconcilor_->HandleRefreshTokenFetched(account_id_, std::string());
115 bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
116 const std::string& s2) const {
117 return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
120 class AccountReconcilor::UserIdFetcher
121 : public gaia::GaiaOAuthClient::Delegate {
123 UserIdFetcher(AccountReconcilor* reconcilor,
124 const std::string& access_token,
125 const std::string& account_id);
127 // Returns the scopes needed by the UserIdFetcher.
128 static OAuth2TokenService::ScopeSet GetScopes();
131 // Overriden from gaia::GaiaOAuthClient::Delegate.
132 virtual void OnGetUserIdResponse(const std::string& user_id) OVERRIDE;
133 virtual void OnOAuthError() OVERRIDE;
134 virtual void OnNetworkError(int response_code) OVERRIDE;
136 AccountReconcilor* const reconcilor_;
137 const std::string account_id_;
138 const std::string access_token_;
139 gaia::GaiaOAuthClient gaia_auth_client_;
141 DISALLOW_COPY_AND_ASSIGN(UserIdFetcher);
144 AccountReconcilor::UserIdFetcher::UserIdFetcher(AccountReconcilor* reconcilor,
145 const std::string& access_token,
146 const std::string& account_id)
147 : reconcilor_(reconcilor),
148 account_id_(account_id),
149 access_token_(access_token),
150 gaia_auth_client_(reconcilor_->client()->GetURLRequestContext()) {
152 DCHECK(!account_id_.empty());
154 const int kMaxRetries = 5;
155 gaia_auth_client_.GetUserId(access_token_, kMaxRetries, this);
159 OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
160 OAuth2TokenService::ScopeSet scopes;
161 scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
165 void AccountReconcilor::UserIdFetcher::OnGetUserIdResponse(
166 const std::string& user_id) {
167 VLOG(1) << "AccountReconcilor::OnGetUserIdResponse: " << account_id_;
169 // HandleSuccessfulAccountIdCheck() may delete |this|, so call it last.
170 reconcilor_->HandleSuccessfulAccountIdCheck(account_id_);
173 void AccountReconcilor::UserIdFetcher::OnOAuthError() {
174 VLOG(1) << "AccountReconcilor::OnOAuthError: " << account_id_;
176 // Invalidate the access token to force a refetch next time.
177 reconcilor_->token_service()->InvalidateToken(
178 account_id_, GetScopes(), access_token_);
180 // HandleFailedAccountIdCheck() may delete |this|, so call it last.
181 reconcilor_->HandleFailedAccountIdCheck(account_id_);
184 void AccountReconcilor::UserIdFetcher::OnNetworkError(int response_code) {
185 VLOG(1) << "AccountReconcilor::OnNetworkError: " << account_id_
186 << " response_code=" << response_code;
188 // TODO(rogerta): some response error should not be treated like
189 // permanent errors. Figure out appropriate ones.
190 // HandleFailedAccountIdCheck() may delete |this|, so call it last.
191 reconcilor_->HandleFailedAccountIdCheck(account_id_);
194 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
195 SigninManagerBase* signin_manager,
196 SigninClient* client)
197 : OAuth2TokenService::Consumer("account_reconcilor"),
198 token_service_(token_service),
199 signin_manager_(signin_manager),
201 merge_session_helper_(token_service_,
202 client->GetURLRequestContext(),
204 registered_with_token_service_(false),
205 is_reconcile_started_(false),
206 first_execution_(true),
207 are_gaia_accounts_set_(false),
209 VLOG(1) << "AccountReconcilor::AccountReconcilor";
212 AccountReconcilor::~AccountReconcilor() {
213 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
214 // Make sure shutdown was called first.
215 DCHECK(!registered_with_token_service_);
217 DCHECK_EQ(0u, user_id_fetchers_.size());
218 DCHECK_EQ(0u, refresh_token_fetchers_.size());
221 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
222 VLOG(1) << "AccountReconcilor::Initialize";
223 RegisterWithSigninManager();
225 // If this user is not signed in, the reconcilor should do nothing but
227 if (IsProfileConnected()) {
228 RegisterForCookieChanges();
229 RegisterWithTokenService();
231 // Start a reconcile if the tokens are already loaded.
232 if (start_reconcile_if_tokens_available &&
233 token_service_->GetAccounts().size() > 0) {
239 void AccountReconcilor::Shutdown() {
240 VLOG(1) << "AccountReconcilor::Shutdown";
241 merge_session_helper_.CancelAll();
242 merge_session_helper_.RemoveObserver(this);
243 gaia_fetcher_.reset();
244 get_gaia_accounts_callbacks_.clear();
246 UnregisterWithSigninManager();
247 UnregisterWithTokenService();
248 UnregisterForCookieChanges();
251 void AccountReconcilor::AddMergeSessionObserver(
252 MergeSessionHelper::Observer* observer) {
253 merge_session_helper_.AddObserver(observer);
256 void AccountReconcilor::RemoveMergeSessionObserver(
257 MergeSessionHelper::Observer* observer) {
258 merge_session_helper_.RemoveObserver(observer);
261 void AccountReconcilor::DeleteFetchers() {
265 user_id_fetchers_.clear();
266 refresh_token_fetchers_.clear();
269 bool AccountReconcilor::AreAllRefreshTokensChecked() const {
270 return chrome_accounts_.size() ==
271 (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
274 void AccountReconcilor::RegisterForCookieChanges() {
275 // First clear any existing registration to avoid DCHECKs that can otherwise
276 // go off in some embedders on reauth (e.g., ChromeSigninClient).
277 UnregisterForCookieChanges();
278 cookie_changed_subscription_ = client_->AddCookieChangedCallback(
279 base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
282 void AccountReconcilor::UnregisterForCookieChanges() {
283 cookie_changed_subscription_.reset();
286 void AccountReconcilor::RegisterWithSigninManager() {
287 signin_manager_->AddObserver(this);
290 void AccountReconcilor::UnregisterWithSigninManager() {
291 signin_manager_->RemoveObserver(this);
294 void AccountReconcilor::RegisterWithTokenService() {
295 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
296 // During re-auth, the reconcilor will get a callback about successful signin
297 // even when the profile is already connected. Avoid re-registering
298 // with the token service since this will DCHECK.
299 if (registered_with_token_service_)
302 token_service_->AddObserver(this);
303 registered_with_token_service_ = true;
306 void AccountReconcilor::UnregisterWithTokenService() {
307 if (!registered_with_token_service_)
310 token_service_->RemoveObserver(this);
311 registered_with_token_service_ = false;
314 bool AccountReconcilor::IsProfileConnected() {
315 return !signin_manager_->GetAuthenticatedUsername().empty();
318 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) {
319 if (cookie->Name() == "LSID" &&
320 cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
321 cookie->IsSecure() && cookie->IsHttpOnly()) {
322 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
324 // It is possible that O2RT is not available at this moment.
325 if (!token_service_->GetAccounts().size()) {
326 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
327 "because O2RT is not available yet.";
335 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
336 VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
337 PerformStartRemoveAction(account_id);
340 void AccountReconcilor::OnEndBatchChanges() {
341 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
345 void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
346 const std::string& password) {
347 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
348 RegisterForCookieChanges();
349 RegisterWithTokenService();
352 void AccountReconcilor::GoogleSignedOut(const std::string& username) {
353 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
354 gaia_fetcher_.reset();
355 get_gaia_accounts_callbacks_.clear();
357 UnregisterWithTokenService();
358 UnregisterForCookieChanges();
359 PerformLogoutAllAccountsAction();
362 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
363 if (!switches::IsEnableAccountConsistency()) {
364 MarkAccountAsAddedToCookie(account_id);
367 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
368 merge_session_helper_.LogIn(account_id);
371 void AccountReconcilor::PerformStartRemoveAction(
372 const std::string& account_id) {
373 VLOG(1) << "AccountReconcilor::PerformStartRemoveAction: " << account_id;
374 GetAccountsFromCookie(base::Bind(
375 &AccountReconcilor::PerformFinishRemoveAction,
376 base::Unretained(this),
380 void AccountReconcilor::PerformFinishRemoveAction(
381 const std::string& account_id,
382 const GoogleServiceAuthError& error,
383 const std::vector<std::pair<std::string, bool> >& accounts) {
384 if (!switches::IsEnableAccountConsistency())
386 VLOG(1) << "AccountReconcilor::PerformFinishRemoveAction:"
387 << " account=" << account_id << " error=" << error.ToString();
388 if (error.state() == GoogleServiceAuthError::NONE) {
390 std::vector<std::string> accounts_only;
391 for (std::vector<std::pair<std::string, bool> >::const_iterator i =
395 accounts_only.push_back(i->first);
397 merge_session_helper_.LogOut(account_id, accounts_only);
399 // Wait for the next ReconcileAction if there is an error.
402 void AccountReconcilor::PerformAddToChromeAction(
403 const std::string& account_id,
405 const std::string& signin_scoped_device_id) {
406 if (!switches::IsEnableAccountConsistency()) {
407 MarkAccountAsAddedToChrome(account_id);
410 VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
411 << " account=" << account_id << " session_index=" << session_index;
413 #if !defined(OS_ANDROID) && !defined(OS_IOS)
414 refresh_token_fetchers_.push_back(new RefreshTokenFetcher(
415 this, account_id, session_index, signin_scoped_device_id));
419 void AccountReconcilor::PerformLogoutAllAccountsAction() {
420 if (!switches::IsEnableAccountConsistency())
422 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
423 merge_session_helper_.LogOutAllAccounts();
426 void AccountReconcilor::StartReconcile() {
427 if (!IsProfileConnected() || is_reconcile_started_ ||
428 get_gaia_accounts_callbacks_.size() > 0 ||
429 merge_session_helper_.is_running())
432 is_reconcile_started_ = true;
434 StartFetchingExternalCcResult();
436 // Reset state for validating gaia cookie.
437 are_gaia_accounts_set_ = false;
438 gaia_accounts_.clear();
439 GetAccountsFromCookie(base::Bind(
440 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
441 base::Unretained(this)));
443 // Reset state for validating oauth2 tokens.
444 primary_account_.clear();
445 chrome_accounts_.clear();
447 valid_chrome_accounts_.clear();
448 invalid_chrome_accounts_.clear();
449 add_to_cookie_.clear();
450 add_to_chrome_.clear();
451 ValidateAccountsFromTokenService();
454 void AccountReconcilor::GetAccountsFromCookie(
455 GetAccountsFromCookieCallback callback) {
456 get_gaia_accounts_callbacks_.push_back(callback);
457 if (!gaia_fetcher_) {
458 // There is no list account request in flight.
459 gaia_fetcher_.reset(new GaiaAuthFetcher(
460 this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
461 gaia_fetcher_->StartListAccounts();
465 void AccountReconcilor::StartFetchingExternalCcResult() {
466 merge_session_helper_.StartFetchingExternalCcResult();
469 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
470 gaia_fetcher_.reset();
472 // Get account information from response data.
473 std::vector<std::pair<std::string, bool> > gaia_accounts;
474 bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
476 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
477 } else if (gaia_accounts.size() > 0) {
478 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
479 << "Gaia " << gaia_accounts.size() << " accounts, "
480 << "Primary is '" << gaia_accounts[0].first << "'";
482 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
485 // There must be at least one callback waiting for result.
486 DCHECK(!get_gaia_accounts_callbacks_.empty());
488 GoogleServiceAuthError error =
489 !valid_json ? GoogleServiceAuthError(
490 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
491 : GoogleServiceAuthError::AuthErrorNone();
492 get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
493 get_gaia_accounts_callbacks_.pop_front();
495 MayBeDoNextListAccounts();
498 void AccountReconcilor::OnListAccountsFailure(
499 const GoogleServiceAuthError& error) {
500 gaia_fetcher_.reset();
501 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
502 std::vector<std::pair<std::string, bool> > empty_accounts;
504 // There must be at least one callback waiting for result.
505 DCHECK(!get_gaia_accounts_callbacks_.empty());
507 get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
508 get_gaia_accounts_callbacks_.pop_front();
510 MayBeDoNextListAccounts();
513 void AccountReconcilor::MayBeDoNextListAccounts() {
514 if (!get_gaia_accounts_callbacks_.empty()) {
515 gaia_fetcher_.reset(new GaiaAuthFetcher(
516 this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
517 gaia_fetcher_->StartListAccounts();
521 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
522 const GoogleServiceAuthError& error,
523 const std::vector<std::pair<std::string, bool> >& accounts) {
524 if (error.state() == GoogleServiceAuthError::NONE) {
525 gaia_accounts_ = accounts;
526 are_gaia_accounts_set_ = true;
533 void AccountReconcilor::ValidateAccountsFromTokenService() {
534 primary_account_ = signin_manager_->GetAuthenticatedUsername();
535 DCHECK(!primary_account_.empty());
537 chrome_accounts_ = token_service_->GetAccounts();
539 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
540 << "Chrome " << chrome_accounts_.size() << " accounts, "
541 << "Primary is '" << primary_account_ << "'";
545 new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
546 const OAuth2TokenService::ScopeSet scopes =
547 AccountReconcilor::UserIdFetcher::GetScopes();
548 for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
550 token_service_->StartRequest(chrome_accounts_[i], scopes, this);
553 DCHECK_EQ(0u, user_id_fetchers_.size());
554 user_id_fetchers_.resize(chrome_accounts_.size());
557 void AccountReconcilor::OnGetTokenSuccess(
558 const OAuth2TokenService::Request* request,
559 const std::string& access_token,
560 const base::Time& expiration_time) {
562 for (index = 0; index < chrome_accounts_.size(); ++index) {
563 if (request == requests_[index].get())
566 DCHECK(index < chrome_accounts_.size());
568 const std::string& account_id = chrome_accounts_[index];
570 VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
572 DCHECK(!user_id_fetchers_[index]);
573 user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
576 void AccountReconcilor::OnGetTokenFailure(
577 const OAuth2TokenService::Request* request,
578 const GoogleServiceAuthError& error) {
580 for (index = 0; index < chrome_accounts_.size(); ++index) {
581 if (request == requests_[index].get())
584 DCHECK(index < chrome_accounts_.size());
586 const std::string& account_id = chrome_accounts_[index];
588 VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
589 HandleFailedAccountIdCheck(account_id);
592 void AccountReconcilor::OnNewProfileManagementFlagChanged(
593 bool new_flag_status) {
594 if (new_flag_status) {
595 // The reconciler may have been newly created just before this call, or may
596 // have already existed and in mid-reconcile. To err on the safe side, force
605 void AccountReconcilor::FinishReconcile() {
606 // Make sure that the process of validating the gaia cookie and the oauth2
607 // tokens individually is done before proceeding with reconciliation.
608 if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
611 VLOG(1) << "AccountReconcilor::FinishReconcile";
615 DCHECK(add_to_cookie_.empty());
616 DCHECK(add_to_chrome_.empty());
617 int number_gaia_accounts = gaia_accounts_.size();
618 bool are_primaries_equal =
619 number_gaia_accounts > 0 &&
620 gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
623 if (are_primaries_equal) {
624 // Determine if we need to merge accounts from gaia cookie to chrome.
625 for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
626 const std::string& gaia_account = gaia_accounts_[i].first;
627 if (gaia_accounts_[i].second &&
628 valid_chrome_accounts_.find(gaia_account) ==
629 valid_chrome_accounts_.end()) {
630 add_to_chrome_.push_back(std::make_pair(gaia_account, i));
634 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
635 // Really messed up state. Blow away the gaia cookie completely and
636 // rebuild it, making sure the primary account as specified by the
637 // SigninManager is the first session in the gaia cookie.
638 PerformLogoutAllAccountsAction();
639 gaia_accounts_.clear();
642 // Create a list of accounts that need to be added to the gaia cookie.
643 // The primary account must be first to make sure it becomes the default
644 // account in the case where chrome is completely rebuilding the cookie.
645 add_to_cookie_.push_back(primary_account_);
646 for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
647 i != valid_chrome_accounts_.end();
649 if (*i != primary_account_)
650 add_to_cookie_.push_back(*i);
653 // For each account known to chrome, PerformMergeAction() if the account is
654 // not already in the cookie jar or its state is invalid, or signal merge
655 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
656 // SignalComplete() will change the array.
657 std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
658 int added_to_cookie = 0;
659 bool external_cc_result_completed =
660 !merge_session_helper_.StillFetchingExternalCcResult();
661 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
662 if (gaia_accounts_.end() !=
663 std::find_if(gaia_accounts_.begin(),
664 gaia_accounts_.end(),
665 std::bind1st(EmailEqualToFunc(),
666 std::make_pair(add_to_cookie_copy[i],
668 merge_session_helper_.SignalComplete(
669 add_to_cookie_copy[i],
670 GoogleServiceAuthError::AuthErrorNone());
672 PerformMergeAction(add_to_cookie_copy[i]);
677 // Log whether the external connection checks were completed when we tried
678 // to add the accounts to the cookie.
679 if (added_to_cookie > 0)
680 signin_metrics::LogExternalCcResultFetches(external_cc_result_completed);
682 std::string signin_scoped_device_id = client_->GetSigninScopedDeviceId();
683 // For each account in the gaia cookie not known to chrome,
684 // PerformAddToChromeAction. Make a copy of |add_to_chrome| since calls to
685 // PerformAddToChromeAction() may modify this array.
686 std::vector<std::pair<std::string, int> > add_to_chrome_copy = add_to_chrome_;
687 for (std::vector<std::pair<std::string, int> >::const_iterator i =
688 add_to_chrome_copy.begin();
689 i != add_to_chrome_copy.end();
691 PerformAddToChromeAction(i->first, i->second, signin_scoped_device_id);
694 signin_metrics::LogSigninAccountReconciliation(valid_chrome_accounts_.size(),
696 add_to_chrome_.size(),
699 number_gaia_accounts);
700 first_execution_ = false;
701 CalculateIfReconcileIsDone();
702 ScheduleStartReconcileIfChromeAccountsChanged();
705 void AccountReconcilor::AbortReconcile() {
706 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
708 add_to_cookie_.clear();
709 add_to_chrome_.clear();
710 CalculateIfReconcileIsDone();
713 void AccountReconcilor::CalculateIfReconcileIsDone() {
714 is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
715 if (!is_reconcile_started_)
716 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
719 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
720 if (is_reconcile_started_)
723 // Start a reconcile as the token accounts have changed.
724 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
725 std::vector<std::string> reconciled_accounts(chrome_accounts_);
726 std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
727 std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
728 std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
729 if (reconciled_accounts != new_chrome_accounts) {
730 base::MessageLoop::current()->PostTask(
732 base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
736 // Remove the account from the list that is being merged.
737 bool AccountReconcilor::MarkAccountAsAddedToCookie(
738 const std::string& account_id) {
739 for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
740 i != add_to_cookie_.end();
742 if (account_id == *i) {
743 add_to_cookie_.erase(i);
750 void AccountReconcilor::MergeSessionCompleted(
751 const std::string& account_id,
752 const GoogleServiceAuthError& error) {
753 VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
756 if (MarkAccountAsAddedToCookie(account_id)) {
757 CalculateIfReconcileIsDone();
758 ScheduleStartReconcileIfChromeAccountsChanged();
762 void AccountReconcilor::HandleSuccessfulAccountIdCheck(
763 const std::string& account_id) {
764 valid_chrome_accounts_.insert(account_id);
768 void AccountReconcilor::HandleFailedAccountIdCheck(
769 const std::string& account_id) {
770 invalid_chrome_accounts_.insert(account_id);
774 void AccountReconcilor::PerformAddAccountToTokenService(
775 const std::string& account_id,
776 const std::string& refresh_token) {
777 // The flow should never get to this method if new_profile_management is
778 // false, but better safe than sorry.
779 if (!switches::IsEnableAccountConsistency())
781 token_service_->UpdateCredentials(account_id, refresh_token);
784 // Remove the account from the list that is being updated.
785 void AccountReconcilor::MarkAccountAsAddedToChrome(
786 const std::string& account_id) {
787 for (std::vector<std::pair<std::string, int> >::iterator i =
788 add_to_chrome_.begin();
789 i != add_to_chrome_.end();
791 if (gaia::AreEmailsSame(account_id, i->first)) {
792 add_to_chrome_.erase(i);
798 void AccountReconcilor::HandleRefreshTokenFetched(
799 const std::string& account_id,
800 const std::string& refresh_token) {
801 if (!refresh_token.empty())
802 PerformAddAccountToTokenService(account_id, refresh_token);
804 MarkAccountAsAddedToChrome(account_id);
805 CalculateIfReconcileIsDone();