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