account_reconcilor.cc revision 010d83a9304c5a91596085d917d248abff47903a
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.
4
5#include "components/signin/core/browser/account_reconcilor.h"
6
7#include <algorithm>
8
9#include "base/bind.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_oauth_helper.h"
19#include "google_apis/gaia/gaia_auth_fetcher.h"
20#include "google_apis/gaia/gaia_auth_util.h"
21#include "google_apis/gaia/gaia_constants.h"
22#include "google_apis/gaia/gaia_oauth_client.h"
23#include "google_apis/gaia/gaia_urls.h"
24#include "net/cookies/canonical_cookie.h"
25
26
27namespace {
28
29class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
30 public:
31  bool operator()(const std::pair<std::string, bool>& p1,
32                  const std::pair<std::string, bool>& p2) const;
33};
34
35bool EmailEqualToFunc::operator()(
36    const std::pair<std::string, bool>& p1,
37    const std::pair<std::string, bool>& p2) const {
38  return p1.second == p2.second && gaia::AreEmailsSame(p1.first, p2.first);
39}
40
41}  // namespace
42
43
44// Fetches a refresh token from the given session in the GAIA cookie.  This is
45// a best effort only.  If it should fail, another reconcile action will occur
46// shortly anyway.
47class AccountReconcilor::RefreshTokenFetcher
48    : public SigninOAuthHelper,
49      public SigninOAuthHelper::Consumer {
50 public:
51  RefreshTokenFetcher(AccountReconcilor* reconcilor,
52                      const std::string& account_id,
53                      int session_index);
54  virtual ~RefreshTokenFetcher() {}
55
56 private:
57  // Overridden from GaiaAuthConsumer:
58  virtual void OnSigninOAuthInformationAvailable(
59      const std::string& email,
60      const std::string& display_email,
61      const std::string& refresh_token) OVERRIDE;
62
63  // Called when an error occurs while getting the information.
64  virtual void OnSigninOAuthInformationFailure(
65      const GoogleServiceAuthError& error) OVERRIDE;
66
67  AccountReconcilor* reconcilor_;
68  const std::string account_id_;
69  int session_index_;
70
71  DISALLOW_COPY_AND_ASSIGN(RefreshTokenFetcher);
72};
73
74AccountReconcilor::RefreshTokenFetcher::RefreshTokenFetcher(
75    AccountReconcilor* reconcilor,
76    const std::string& account_id,
77    int session_index)
78    : SigninOAuthHelper(reconcilor->client()->GetURLRequestContext(),
79                        base::IntToString(session_index),
80                        this),
81      reconcilor_(reconcilor),
82      account_id_(account_id),
83      session_index_(session_index) {
84  DCHECK(reconcilor_);
85  DCHECK(!account_id.empty());
86}
87
88void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationAvailable(
89    const std::string& email,
90    const std::string& display_email,
91    const std::string& refresh_token) {
92  VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationAvailable:"
93          << " account=" << account_id_ << " email=" << email
94          << " displayEmail=" << display_email;
95
96  // TODO(rogerta): because of the problem with email vs displayEmail and
97  // emails that have been canonicalized, the argument |email| is used here
98  // to make sure the correct string is used when calling the token service.
99  // This will be cleaned up when chrome moves to using gaia obfuscated id.
100  reconcilor_->HandleRefreshTokenFetched(email, refresh_token);
101}
102
103void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationFailure(
104    const GoogleServiceAuthError& error) {
105  VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationFailure:"
106          << " account=" << account_id_ << " session_index=" << session_index_;
107  reconcilor_->HandleRefreshTokenFetched(account_id_, std::string());
108}
109
110bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
111                                                  const std::string& s2) const {
112  return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
113}
114
115class AccountReconcilor::UserIdFetcher
116    : public gaia::GaiaOAuthClient::Delegate {
117 public:
118  UserIdFetcher(AccountReconcilor* reconcilor,
119                const std::string& access_token,
120                const std::string& account_id);
121
122  // Returns the scopes needed by the UserIdFetcher.
123  static OAuth2TokenService::ScopeSet GetScopes();
124
125 private:
126  // Overriden from gaia::GaiaOAuthClient::Delegate.
127  virtual void OnGetUserIdResponse(const std::string& user_id) OVERRIDE;
128  virtual void OnOAuthError() OVERRIDE;
129  virtual void OnNetworkError(int response_code) OVERRIDE;
130
131  AccountReconcilor* const reconcilor_;
132  const std::string account_id_;
133  const std::string access_token_;
134  gaia::GaiaOAuthClient gaia_auth_client_;
135
136  DISALLOW_COPY_AND_ASSIGN(UserIdFetcher);
137};
138
139AccountReconcilor::UserIdFetcher::UserIdFetcher(AccountReconcilor* reconcilor,
140                                                const std::string& access_token,
141                                                const std::string& account_id)
142    : reconcilor_(reconcilor),
143      account_id_(account_id),
144      access_token_(access_token),
145      gaia_auth_client_(reconcilor_->client()->GetURLRequestContext()) {
146  DCHECK(reconcilor_);
147  DCHECK(!account_id_.empty());
148
149  const int kMaxRetries = 5;
150  gaia_auth_client_.GetUserId(access_token_, kMaxRetries, this);
151}
152
153// static
154OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
155  OAuth2TokenService::ScopeSet scopes;
156  scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
157  return scopes;
158}
159
160void AccountReconcilor::UserIdFetcher::OnGetUserIdResponse(
161    const std::string& user_id) {
162  VLOG(1) << "AccountReconcilor::OnGetUserIdResponse: " << account_id_;
163
164  // HandleSuccessfulAccountIdCheck() may delete |this|, so call it last.
165  reconcilor_->HandleSuccessfulAccountIdCheck(account_id_);
166}
167
168void AccountReconcilor::UserIdFetcher::OnOAuthError() {
169  VLOG(1) << "AccountReconcilor::OnOAuthError: " << account_id_;
170
171  // Invalidate the access token to force a refetch next time.
172  reconcilor_->token_service()->InvalidateToken(
173      account_id_, GetScopes(), access_token_);
174
175  // HandleFailedAccountIdCheck() may delete |this|, so call it last.
176  reconcilor_->HandleFailedAccountIdCheck(account_id_);
177}
178
179void AccountReconcilor::UserIdFetcher::OnNetworkError(int response_code) {
180  VLOG(1) << "AccountReconcilor::OnNetworkError: " << account_id_
181          << " response_code=" << response_code;
182
183  // TODO(rogerta): some response error should not be treated like
184  // permanent errors.  Figure out appropriate ones.
185  // HandleFailedAccountIdCheck() may delete |this|, so call it last.
186  reconcilor_->HandleFailedAccountIdCheck(account_id_);
187}
188
189AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
190                                     SigninManagerBase* signin_manager,
191                                     SigninClient* client)
192    : OAuth2TokenService::Consumer("account_reconcilor"),
193      token_service_(token_service),
194      signin_manager_(signin_manager),
195      client_(client),
196      merge_session_helper_(token_service_,
197                            client->GetURLRequestContext(),
198                            this),
199      registered_with_token_service_(false),
200      is_reconcile_started_(false),
201      are_gaia_accounts_set_(false),
202      requests_(NULL) {
203  VLOG(1) << "AccountReconcilor::AccountReconcilor";
204}
205
206AccountReconcilor::~AccountReconcilor() {
207  VLOG(1) << "AccountReconcilor::~AccountReconcilor";
208  // Make sure shutdown was called first.
209  DCHECK(!registered_with_token_service_);
210  DCHECK(!reconciliation_timer_.IsRunning());
211  DCHECK(!requests_);
212  DCHECK_EQ(0u, user_id_fetchers_.size());
213  DCHECK_EQ(0u, refresh_token_fetchers_.size());
214}
215
216void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
217  VLOG(1) << "AccountReconcilor::Initialize";
218  RegisterWithSigninManager();
219
220  // If this user is not signed in, the reconcilor should do nothing but
221  // wait for signin.
222  if (IsProfileConnected()) {
223    RegisterForCookieChanges();
224    RegisterWithTokenService();
225    StartPeriodicReconciliation();
226
227    // Start a reconcile if the tokens are already loaded.
228    if (start_reconcile_if_tokens_available &&
229        token_service_->GetAccounts().size() > 0) {
230      StartReconcile();
231    }
232  }
233}
234
235void AccountReconcilor::Shutdown() {
236  VLOG(1) << "AccountReconcilor::Shutdown";
237  merge_session_helper_.CancelAll();
238  merge_session_helper_.RemoveObserver(this);
239  gaia_fetcher_.reset();
240  DeleteFetchers();
241  UnregisterWithSigninManager();
242  UnregisterWithTokenService();
243  UnregisterForCookieChanges();
244  StopPeriodicReconciliation();
245}
246
247void AccountReconcilor::AddMergeSessionObserver(
248    MergeSessionHelper::Observer* observer) {
249  merge_session_helper_.AddObserver(observer);
250}
251
252void AccountReconcilor::RemoveMergeSessionObserver(
253    MergeSessionHelper::Observer* observer) {
254  merge_session_helper_.RemoveObserver(observer);
255}
256
257void AccountReconcilor::DeleteFetchers() {
258  delete[] requests_;
259  requests_ = NULL;
260
261  user_id_fetchers_.clear();
262  refresh_token_fetchers_.clear();
263}
264
265bool AccountReconcilor::AreAllRefreshTokensChecked() const {
266  return chrome_accounts_.size() ==
267         (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
268}
269
270void AccountReconcilor::RegisterForCookieChanges() {
271  // First clear any existing registration to avoid DCHECKs that can otherwise
272  // go off in some embedders on reauth (e.g., ChromeSigninClient).
273  UnregisterForCookieChanges();
274  client_->SetCookieChangedCallback(
275      base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
276}
277
278void AccountReconcilor::UnregisterForCookieChanges() {
279  client_->SetCookieChangedCallback(SigninClient::CookieChangedCallback());
280}
281
282void AccountReconcilor::RegisterWithSigninManager() {
283  signin_manager_->AddObserver(this);
284}
285
286void AccountReconcilor::UnregisterWithSigninManager() {
287  signin_manager_->RemoveObserver(this);
288}
289
290void AccountReconcilor::RegisterWithTokenService() {
291  VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
292  // During re-auth, the reconcilor will get a callback about successful signin
293  // even when the profile is already connected.  Avoid re-registering
294  // with the token service since this will DCHECK.
295  if (registered_with_token_service_)
296    return;
297
298  token_service_->AddObserver(this);
299  registered_with_token_service_ = true;
300}
301
302void AccountReconcilor::UnregisterWithTokenService() {
303  if (!registered_with_token_service_)
304    return;
305
306  token_service_->RemoveObserver(this);
307  registered_with_token_service_ = false;
308}
309
310bool AccountReconcilor::IsProfileConnected() {
311  return !signin_manager_->GetAuthenticatedUsername().empty();
312}
313
314void AccountReconcilor::StartPeriodicReconciliation() {
315  VLOG(1) << "AccountReconcilor::StartPeriodicReconciliation";
316  // TODO(rogerta): pick appropriate thread and timeout value.
317  reconciliation_timer_.Start(FROM_HERE,
318                              base::TimeDelta::FromSeconds(300),
319                              this,
320                              &AccountReconcilor::PeriodicReconciliation);
321}
322
323void AccountReconcilor::StopPeriodicReconciliation() {
324  VLOG(1) << "AccountReconcilor::StopPeriodicReconciliation";
325  reconciliation_timer_.Stop();
326}
327
328void AccountReconcilor::PeriodicReconciliation() {
329  VLOG(1) << "AccountReconcilor::PeriodicReconciliation";
330  StartReconcile();
331}
332
333void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) {
334  if (cookie->Name() == "LSID" &&
335      cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
336      cookie->IsSecure() && cookie->IsHttpOnly()) {
337    VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
338#ifdef OS_CHROMEOS
339    // On Chrome OS it is possible that O2RT is not available at this moment
340    // because profile data transfer is still in progress.
341    if (!token_service_->GetAccounts().size()) {
342      VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
343                 "because profile data transfer is in progress.";
344      return;
345    }
346#endif
347    StartReconcile();
348  }
349}
350
351void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
352  VLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
353  StartReconcile();
354}
355
356void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
357  VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
358  StartRemoveAction(account_id);
359}
360
361void AccountReconcilor::OnRefreshTokensLoaded() {}
362
363void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
364                                              const std::string& password) {
365  VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
366  RegisterForCookieChanges();
367  RegisterWithTokenService();
368  StartPeriodicReconciliation();
369}
370
371void AccountReconcilor::GoogleSignedOut(const std::string& username) {
372  VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
373  UnregisterWithTokenService();
374  UnregisterForCookieChanges();
375  StopPeriodicReconciliation();
376}
377
378void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
379  VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
380  merge_session_helper_.LogIn(account_id);
381}
382
383void AccountReconcilor::StartRemoveAction(const std::string& account_id) {
384  VLOG(1) << "AccountReconcilor::StartRemoveAction: " << account_id;
385  GetAccountsFromCookie(base::Bind(&AccountReconcilor::FinishRemoveAction,
386                                   base::Unretained(this),
387                                   account_id));
388}
389
390void AccountReconcilor::FinishRemoveAction(
391    const std::string& account_id,
392    const GoogleServiceAuthError& error,
393    const std::vector<std::pair<std::string, bool> >& accounts) {
394  VLOG(1) << "AccountReconcilor::FinishRemoveAction:"
395          << " account=" << account_id << " error=" << error.ToString();
396  if (error.state() == GoogleServiceAuthError::NONE) {
397    AbortReconcile();
398    std::vector<std::string> accounts_only;
399    for (std::vector<std::pair<std::string, bool> >::const_iterator i =
400             accounts.begin();
401         i != accounts.end();
402         ++i) {
403      accounts_only.push_back(i->first);
404    }
405    merge_session_helper_.LogOut(account_id, accounts_only);
406  }
407  // Wait for the next ReconcileAction if there is an error.
408}
409
410void AccountReconcilor::PerformAddToChromeAction(const std::string& account_id,
411                                                 int session_index) {
412  VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
413          << " account=" << account_id << " session_index=" << session_index;
414
415#if !defined(OS_ANDROID) && !defined(OS_IOS)
416  refresh_token_fetchers_.push_back(
417      new RefreshTokenFetcher(this, account_id, session_index));
418#endif
419}
420
421void AccountReconcilor::PerformLogoutAllAccountsAction() {
422  VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
423  merge_session_helper_.LogOutAllAccounts();
424}
425
426void AccountReconcilor::StartReconcile() {
427  if (!IsProfileConnected() || is_reconcile_started_)
428    return;
429
430  is_reconcile_started_ = true;
431
432  // Reset state for validating gaia cookie.
433  are_gaia_accounts_set_ = false;
434  gaia_accounts_.clear();
435  GetAccountsFromCookie(base::Bind(
436      &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
437      base::Unretained(this)));
438
439  // Reset state for validating oauth2 tokens.
440  primary_account_.clear();
441  chrome_accounts_.clear();
442  DeleteFetchers();
443  valid_chrome_accounts_.clear();
444  invalid_chrome_accounts_.clear();
445  add_to_cookie_.clear();
446  add_to_chrome_.clear();
447  ValidateAccountsFromTokenService();
448}
449
450void AccountReconcilor::GetAccountsFromCookie(
451    GetAccountsFromCookieCallback callback) {
452  get_gaia_accounts_callbacks_.push_back(callback);
453  if (!gaia_fetcher_) {
454    // There is no list account request in flight.
455    gaia_fetcher_.reset(new GaiaAuthFetcher(
456        this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
457    gaia_fetcher_->StartListAccounts();
458  }
459}
460
461void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
462  gaia_fetcher_.reset();
463
464  // Get account information from response data.
465  std::vector<std::pair<std::string, bool> > gaia_accounts;
466  bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
467  if (!valid_json) {
468    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
469  } else if (gaia_accounts.size() > 0) {
470    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
471            << "Gaia " << gaia_accounts.size() << " accounts, "
472            << "Primary is '" << gaia_accounts[0].first << "'";
473  } else {
474    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
475  }
476
477  // There must be at least one callback waiting for result.
478  DCHECK(!get_gaia_accounts_callbacks_.empty());
479
480  GoogleServiceAuthError error =
481      !valid_json ? GoogleServiceAuthError(
482                        GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
483                  : GoogleServiceAuthError::AuthErrorNone();
484  get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
485  get_gaia_accounts_callbacks_.pop_front();
486
487  MayBeDoNextListAccounts();
488}
489
490void AccountReconcilor::OnListAccountsFailure(
491    const GoogleServiceAuthError& error) {
492  gaia_fetcher_.reset();
493  VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
494  std::vector<std::pair<std::string, bool> > empty_accounts;
495
496  // There must be at least one callback waiting for result.
497  DCHECK(!get_gaia_accounts_callbacks_.empty());
498
499  get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
500  get_gaia_accounts_callbacks_.pop_front();
501
502  MayBeDoNextListAccounts();
503}
504
505void AccountReconcilor::MayBeDoNextListAccounts() {
506  if (!get_gaia_accounts_callbacks_.empty()) {
507    gaia_fetcher_.reset(new GaiaAuthFetcher(
508        this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
509    gaia_fetcher_->StartListAccounts();
510  }
511}
512
513void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
514    const GoogleServiceAuthError& error,
515    const std::vector<std::pair<std::string, bool> >& accounts) {
516  if (error.state() == GoogleServiceAuthError::NONE) {
517    gaia_accounts_ = accounts;
518    are_gaia_accounts_set_ = true;
519    FinishReconcile();
520  } else {
521    AbortReconcile();
522  }
523}
524
525void AccountReconcilor::ValidateAccountsFromTokenService() {
526  primary_account_ = signin_manager_->GetAuthenticatedUsername();
527  DCHECK(!primary_account_.empty());
528
529  chrome_accounts_ = token_service_->GetAccounts();
530  DCHECK_GT(chrome_accounts_.size(), 0u);
531
532  VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
533          << "Chrome " << chrome_accounts_.size() << " accounts, "
534          << "Primary is '" << primary_account_ << "'";
535
536  DCHECK(!requests_);
537  requests_ =
538      new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
539  const OAuth2TokenService::ScopeSet scopes =
540      AccountReconcilor::UserIdFetcher::GetScopes();
541  for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
542    requests_[i] =
543        token_service_->StartRequest(chrome_accounts_[i], scopes, this);
544  }
545
546  DCHECK_EQ(0u, user_id_fetchers_.size());
547  user_id_fetchers_.resize(chrome_accounts_.size());
548}
549
550void AccountReconcilor::OnGetTokenSuccess(
551    const OAuth2TokenService::Request* request,
552    const std::string& access_token,
553    const base::Time& expiration_time) {
554  size_t index;
555  for (index = 0; index < chrome_accounts_.size(); ++index) {
556    if (request == requests_[index].get())
557      break;
558  }
559  DCHECK(index < chrome_accounts_.size());
560
561  const std::string& account_id = chrome_accounts_[index];
562
563  VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
564
565  DCHECK(!user_id_fetchers_[index]);
566  user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
567}
568
569void AccountReconcilor::OnGetTokenFailure(
570    const OAuth2TokenService::Request* request,
571    const GoogleServiceAuthError& error) {
572  size_t index;
573  for (index = 0; index < chrome_accounts_.size(); ++index) {
574    if (request == requests_[index].get())
575      break;
576  }
577  DCHECK(index < chrome_accounts_.size());
578
579  const std::string& account_id = chrome_accounts_[index];
580
581  VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
582  HandleFailedAccountIdCheck(account_id);
583}
584
585void AccountReconcilor::FinishReconcile() {
586  // Make sure that the process of validating the gaia cookie and the oauth2
587  // tokens individually is done before proceeding with reconciliation.
588  if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
589    return;
590
591  VLOG(1) << "AccountReconcilor::FinishReconcile";
592
593  DeleteFetchers();
594
595  DCHECK(add_to_cookie_.empty());
596  DCHECK(add_to_chrome_.empty());
597  bool are_primaries_equal =
598      gaia_accounts_.size() > 0 &&
599      gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
600
601  if (are_primaries_equal) {
602    // Determine if we need to merge accounts from gaia cookie to chrome.
603    for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
604      const std::string& gaia_account = gaia_accounts_[i].first;
605      if (gaia_accounts_[i].second &&
606          valid_chrome_accounts_.find(gaia_account) ==
607              valid_chrome_accounts_.end()) {
608        add_to_chrome_.push_back(std::make_pair(gaia_account, i));
609      }
610    }
611  } else {
612    VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
613    // Really messed up state.  Blow away the gaia cookie completely and
614    // rebuild it, making sure the primary account as specified by the
615    // SigninManager is the first session in the gaia cookie.
616    PerformLogoutAllAccountsAction();
617    gaia_accounts_.clear();
618  }
619
620  // Create a list of accounts that need to be added to the gaia cookie.
621  // The primary account must be first to make sure it becomes the default
622  // account in the case where chrome is completely rebuilding the cookie.
623  add_to_cookie_.push_back(primary_account_);
624  for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
625        i != valid_chrome_accounts_.end();
626        ++i) {
627    if (*i != primary_account_)
628      add_to_cookie_.push_back(*i);
629  }
630
631  // For each account known to chrome, PerformMergeAction() if the account is
632  // not already in the cookie jar or its state is invalid, or signal merge
633  // completed otherwise.  Make a copy of |add_to_cookie_| since calls to
634  // SignalComplete() will change the array.
635  std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
636  for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
637    if (gaia_accounts_.end() !=
638            std::find_if(gaia_accounts_.begin(),
639                         gaia_accounts_.end(),
640                         std::bind1st(EmailEqualToFunc(),
641                                      std::make_pair(add_to_cookie_copy[i],
642                                                     true)))) {
643      merge_session_helper_.SignalComplete(
644          add_to_cookie_copy[i],
645          GoogleServiceAuthError::AuthErrorNone());
646    } else {
647      PerformMergeAction(add_to_cookie_copy[i]);
648    }
649  }
650
651  // For each account in the gaia cookie not known to chrome,
652  // PerformAddToChromeAction.
653  for (std::vector<std::pair<std::string, int> >::const_iterator i =
654           add_to_chrome_.begin();
655       i != add_to_chrome_.end();
656       ++i) {
657    PerformAddToChromeAction(i->first, i->second);
658  }
659
660  CalculateIfReconcileIsDone();
661  ScheduleStartReconcileIfChromeAccountsChanged();
662}
663
664void AccountReconcilor::AbortReconcile() {
665  VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
666  DeleteFetchers();
667  add_to_cookie_.clear();
668  add_to_chrome_.clear();
669  CalculateIfReconcileIsDone();
670}
671
672void AccountReconcilor::CalculateIfReconcileIsDone() {
673  is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
674  if (!is_reconcile_started_)
675    VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
676}
677
678void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
679  if (is_reconcile_started_)
680    return;
681
682  // Start a reconcile as the token accounts have changed.
683  VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
684  std::vector<std::string> reconciled_accounts(chrome_accounts_);
685  std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
686  std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
687  std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
688  if (reconciled_accounts != new_chrome_accounts) {
689    base::MessageLoop::current()->PostTask(
690        FROM_HERE,
691        base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
692  }
693}
694
695void AccountReconcilor::MergeSessionCompleted(
696    const std::string& account_id,
697    const GoogleServiceAuthError& error) {
698  VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
699          << account_id;
700
701  // Remove the account from the list that is being merged.
702  for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
703       i != add_to_cookie_.end();
704       ++i) {
705    if (account_id == *i) {
706      add_to_cookie_.erase(i);
707      break;
708    }
709  }
710
711  CalculateIfReconcileIsDone();
712  ScheduleStartReconcileIfChromeAccountsChanged();
713}
714
715void AccountReconcilor::HandleSuccessfulAccountIdCheck(
716    const std::string& account_id) {
717  valid_chrome_accounts_.insert(account_id);
718  FinishReconcile();
719}
720
721void AccountReconcilor::HandleFailedAccountIdCheck(
722    const std::string& account_id) {
723  invalid_chrome_accounts_.insert(account_id);
724  FinishReconcile();
725}
726
727void AccountReconcilor::HandleRefreshTokenFetched(
728    const std::string& account_id,
729    const std::string& refresh_token) {
730  if (!refresh_token.empty()) {
731    token_service_->UpdateCredentials(account_id, refresh_token);
732  }
733
734  // Remove the account from the list that is being updated.
735  for (std::vector<std::pair<std::string, int> >::iterator i =
736           add_to_chrome_.begin();
737       i != add_to_chrome_.end();
738       ++i) {
739    if (gaia::AreEmailsSame(account_id, i->first)) {
740      add_to_chrome_.erase(i);
741      break;
742    }
743  }
744
745  CalculateIfReconcileIsDone();
746}
747