Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / components / signin / core / browser / account_reconcilor.cc
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
27 namespace {
28
29 class 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
35 bool 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.
47 class 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
74 AccountReconcilor::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
88 void 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
103 void 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
110 bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
111                                                   const std::string& s2) const {
112   return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
113 }
114
115 class 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
139 AccountReconcilor::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
154 OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
155   OAuth2TokenService::ScopeSet scopes;
156   scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
157   return scopes;
158 }
159
160 void 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
168 void 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
179 void 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
189 AccountReconcilor::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
206 AccountReconcilor::~AccountReconcilor() {
207   VLOG(1) << "AccountReconcilor::~AccountReconcilor";
208   // Make sure shutdown was called first.
209   DCHECK(!registered_with_token_service_);
210   DCHECK(!requests_);
211   DCHECK_EQ(0u, user_id_fetchers_.size());
212   DCHECK_EQ(0u, refresh_token_fetchers_.size());
213 }
214
215 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
216   VLOG(1) << "AccountReconcilor::Initialize";
217   RegisterWithSigninManager();
218
219   // If this user is not signed in, the reconcilor should do nothing but
220   // wait for signin.
221   if (IsProfileConnected()) {
222     RegisterForCookieChanges();
223     RegisterWithTokenService();
224
225     // Start a reconcile if the tokens are already loaded.
226     if (start_reconcile_if_tokens_available &&
227         token_service_->GetAccounts().size() > 0) {
228       StartReconcile();
229     }
230   }
231 }
232
233 void AccountReconcilor::Shutdown() {
234   VLOG(1) << "AccountReconcilor::Shutdown";
235   merge_session_helper_.CancelAll();
236   merge_session_helper_.RemoveObserver(this);
237   gaia_fetcher_.reset();
238   DeleteFetchers();
239   UnregisterWithSigninManager();
240   UnregisterWithTokenService();
241   UnregisterForCookieChanges();
242 }
243
244 void AccountReconcilor::AddMergeSessionObserver(
245     MergeSessionHelper::Observer* observer) {
246   merge_session_helper_.AddObserver(observer);
247 }
248
249 void AccountReconcilor::RemoveMergeSessionObserver(
250     MergeSessionHelper::Observer* observer) {
251   merge_session_helper_.RemoveObserver(observer);
252 }
253
254 void AccountReconcilor::DeleteFetchers() {
255   delete[] requests_;
256   requests_ = NULL;
257
258   user_id_fetchers_.clear();
259   refresh_token_fetchers_.clear();
260 }
261
262 bool AccountReconcilor::AreAllRefreshTokensChecked() const {
263   return chrome_accounts_.size() ==
264          (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
265 }
266
267 void AccountReconcilor::RegisterForCookieChanges() {
268   // First clear any existing registration to avoid DCHECKs that can otherwise
269   // go off in some embedders on reauth (e.g., ChromeSigninClient).
270   UnregisterForCookieChanges();
271   client_->SetCookieChangedCallback(
272       base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
273 }
274
275 void AccountReconcilor::UnregisterForCookieChanges() {
276   client_->SetCookieChangedCallback(SigninClient::CookieChangedCallback());
277 }
278
279 void AccountReconcilor::RegisterWithSigninManager() {
280   signin_manager_->AddObserver(this);
281 }
282
283 void AccountReconcilor::UnregisterWithSigninManager() {
284   signin_manager_->RemoveObserver(this);
285 }
286
287 void AccountReconcilor::RegisterWithTokenService() {
288   VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
289   // During re-auth, the reconcilor will get a callback about successful signin
290   // even when the profile is already connected.  Avoid re-registering
291   // with the token service since this will DCHECK.
292   if (registered_with_token_service_)
293     return;
294
295   token_service_->AddObserver(this);
296   registered_with_token_service_ = true;
297 }
298
299 void AccountReconcilor::UnregisterWithTokenService() {
300   if (!registered_with_token_service_)
301     return;
302
303   token_service_->RemoveObserver(this);
304   registered_with_token_service_ = false;
305 }
306
307 bool AccountReconcilor::IsProfileConnected() {
308   return !signin_manager_->GetAuthenticatedUsername().empty();
309 }
310
311 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) {
312   if (cookie->Name() == "LSID" &&
313       cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
314       cookie->IsSecure() && cookie->IsHttpOnly()) {
315     VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
316 #ifdef OS_CHROMEOS
317     // On Chrome OS it is possible that O2RT is not available at this moment
318     // because profile data transfer is still in progress.
319     if (!token_service_->GetAccounts().size()) {
320       VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
321                  "because profile data transfer is in progress.";
322       return;
323     }
324 #endif
325     StartReconcile();
326   }
327 }
328
329 void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
330   VLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
331   StartReconcile();
332 }
333
334 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
335   VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
336   StartRemoveAction(account_id);
337 }
338
339 void AccountReconcilor::OnRefreshTokensLoaded() {}
340
341 void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
342                                               const std::string& password) {
343   VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
344   RegisterForCookieChanges();
345   RegisterWithTokenService();
346 }
347
348 void AccountReconcilor::GoogleSignedOut(const std::string& username) {
349   VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
350   UnregisterWithTokenService();
351   UnregisterForCookieChanges();
352 }
353
354 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
355   VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
356   merge_session_helper_.LogIn(account_id);
357 }
358
359 void AccountReconcilor::StartRemoveAction(const std::string& account_id) {
360   VLOG(1) << "AccountReconcilor::StartRemoveAction: " << account_id;
361   GetAccountsFromCookie(base::Bind(&AccountReconcilor::FinishRemoveAction,
362                                    base::Unretained(this),
363                                    account_id));
364 }
365
366 void AccountReconcilor::FinishRemoveAction(
367     const std::string& account_id,
368     const GoogleServiceAuthError& error,
369     const std::vector<std::pair<std::string, bool> >& accounts) {
370   VLOG(1) << "AccountReconcilor::FinishRemoveAction:"
371           << " account=" << account_id << " error=" << error.ToString();
372   if (error.state() == GoogleServiceAuthError::NONE) {
373     AbortReconcile();
374     std::vector<std::string> accounts_only;
375     for (std::vector<std::pair<std::string, bool> >::const_iterator i =
376              accounts.begin();
377          i != accounts.end();
378          ++i) {
379       accounts_only.push_back(i->first);
380     }
381     merge_session_helper_.LogOut(account_id, accounts_only);
382   }
383   // Wait for the next ReconcileAction if there is an error.
384 }
385
386 void AccountReconcilor::PerformAddToChromeAction(const std::string& account_id,
387                                                  int session_index) {
388   VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
389           << " account=" << account_id << " session_index=" << session_index;
390
391 #if !defined(OS_ANDROID) && !defined(OS_IOS)
392   refresh_token_fetchers_.push_back(
393       new RefreshTokenFetcher(this, account_id, session_index));
394 #endif
395 }
396
397 void AccountReconcilor::PerformLogoutAllAccountsAction() {
398   VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
399   merge_session_helper_.LogOutAllAccounts();
400 }
401
402 void AccountReconcilor::StartReconcile() {
403   if (!IsProfileConnected() || is_reconcile_started_)
404     return;
405
406   is_reconcile_started_ = true;
407
408   // Reset state for validating gaia cookie.
409   are_gaia_accounts_set_ = false;
410   gaia_accounts_.clear();
411   GetAccountsFromCookie(base::Bind(
412       &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
413       base::Unretained(this)));
414
415   // Reset state for validating oauth2 tokens.
416   primary_account_.clear();
417   chrome_accounts_.clear();
418   DeleteFetchers();
419   valid_chrome_accounts_.clear();
420   invalid_chrome_accounts_.clear();
421   add_to_cookie_.clear();
422   add_to_chrome_.clear();
423   ValidateAccountsFromTokenService();
424 }
425
426 void AccountReconcilor::GetAccountsFromCookie(
427     GetAccountsFromCookieCallback callback) {
428   get_gaia_accounts_callbacks_.push_back(callback);
429   if (!gaia_fetcher_) {
430     // There is no list account request in flight.
431     gaia_fetcher_.reset(new GaiaAuthFetcher(
432         this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
433     gaia_fetcher_->StartListAccounts();
434   }
435 }
436
437 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
438   gaia_fetcher_.reset();
439
440   // Get account information from response data.
441   std::vector<std::pair<std::string, bool> > gaia_accounts;
442   bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
443   if (!valid_json) {
444     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
445   } else if (gaia_accounts.size() > 0) {
446     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
447             << "Gaia " << gaia_accounts.size() << " accounts, "
448             << "Primary is '" << gaia_accounts[0].first << "'";
449   } else {
450     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
451   }
452
453   // There must be at least one callback waiting for result.
454   DCHECK(!get_gaia_accounts_callbacks_.empty());
455
456   GoogleServiceAuthError error =
457       !valid_json ? GoogleServiceAuthError(
458                         GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
459                   : GoogleServiceAuthError::AuthErrorNone();
460   get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
461   get_gaia_accounts_callbacks_.pop_front();
462
463   MayBeDoNextListAccounts();
464 }
465
466 void AccountReconcilor::OnListAccountsFailure(
467     const GoogleServiceAuthError& error) {
468   gaia_fetcher_.reset();
469   VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
470   std::vector<std::pair<std::string, bool> > empty_accounts;
471
472   // There must be at least one callback waiting for result.
473   DCHECK(!get_gaia_accounts_callbacks_.empty());
474
475   get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
476   get_gaia_accounts_callbacks_.pop_front();
477
478   MayBeDoNextListAccounts();
479 }
480
481 void AccountReconcilor::MayBeDoNextListAccounts() {
482   if (!get_gaia_accounts_callbacks_.empty()) {
483     gaia_fetcher_.reset(new GaiaAuthFetcher(
484         this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
485     gaia_fetcher_->StartListAccounts();
486   }
487 }
488
489 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
490     const GoogleServiceAuthError& error,
491     const std::vector<std::pair<std::string, bool> >& accounts) {
492   if (error.state() == GoogleServiceAuthError::NONE) {
493     gaia_accounts_ = accounts;
494     are_gaia_accounts_set_ = true;
495     FinishReconcile();
496   } else {
497     AbortReconcile();
498   }
499 }
500
501 void AccountReconcilor::ValidateAccountsFromTokenService() {
502   primary_account_ = signin_manager_->GetAuthenticatedUsername();
503   DCHECK(!primary_account_.empty());
504
505   chrome_accounts_ = token_service_->GetAccounts();
506   DCHECK_GT(chrome_accounts_.size(), 0u);
507
508   VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
509           << "Chrome " << chrome_accounts_.size() << " accounts, "
510           << "Primary is '" << primary_account_ << "'";
511
512   DCHECK(!requests_);
513   requests_ =
514       new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
515   const OAuth2TokenService::ScopeSet scopes =
516       AccountReconcilor::UserIdFetcher::GetScopes();
517   for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
518     requests_[i] =
519         token_service_->StartRequest(chrome_accounts_[i], scopes, this);
520   }
521
522   DCHECK_EQ(0u, user_id_fetchers_.size());
523   user_id_fetchers_.resize(chrome_accounts_.size());
524 }
525
526 void AccountReconcilor::OnGetTokenSuccess(
527     const OAuth2TokenService::Request* request,
528     const std::string& access_token,
529     const base::Time& expiration_time) {
530   size_t index;
531   for (index = 0; index < chrome_accounts_.size(); ++index) {
532     if (request == requests_[index].get())
533       break;
534   }
535   DCHECK(index < chrome_accounts_.size());
536
537   const std::string& account_id = chrome_accounts_[index];
538
539   VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
540
541   DCHECK(!user_id_fetchers_[index]);
542   user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
543 }
544
545 void AccountReconcilor::OnGetTokenFailure(
546     const OAuth2TokenService::Request* request,
547     const GoogleServiceAuthError& error) {
548   size_t index;
549   for (index = 0; index < chrome_accounts_.size(); ++index) {
550     if (request == requests_[index].get())
551       break;
552   }
553   DCHECK(index < chrome_accounts_.size());
554
555   const std::string& account_id = chrome_accounts_[index];
556
557   VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
558   HandleFailedAccountIdCheck(account_id);
559 }
560
561 void AccountReconcilor::FinishReconcile() {
562   // Make sure that the process of validating the gaia cookie and the oauth2
563   // tokens individually is done before proceeding with reconciliation.
564   if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
565     return;
566
567   VLOG(1) << "AccountReconcilor::FinishReconcile";
568
569   DeleteFetchers();
570
571   DCHECK(add_to_cookie_.empty());
572   DCHECK(add_to_chrome_.empty());
573   bool are_primaries_equal =
574       gaia_accounts_.size() > 0 &&
575       gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
576
577   if (are_primaries_equal) {
578     // Determine if we need to merge accounts from gaia cookie to chrome.
579     for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
580       const std::string& gaia_account = gaia_accounts_[i].first;
581       if (gaia_accounts_[i].second &&
582           valid_chrome_accounts_.find(gaia_account) ==
583               valid_chrome_accounts_.end()) {
584         add_to_chrome_.push_back(std::make_pair(gaia_account, i));
585       }
586     }
587   } else {
588     VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
589     // Really messed up state.  Blow away the gaia cookie completely and
590     // rebuild it, making sure the primary account as specified by the
591     // SigninManager is the first session in the gaia cookie.
592     PerformLogoutAllAccountsAction();
593     gaia_accounts_.clear();
594   }
595
596   // Create a list of accounts that need to be added to the gaia cookie.
597   // The primary account must be first to make sure it becomes the default
598   // account in the case where chrome is completely rebuilding the cookie.
599   add_to_cookie_.push_back(primary_account_);
600   for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
601         i != valid_chrome_accounts_.end();
602         ++i) {
603     if (*i != primary_account_)
604       add_to_cookie_.push_back(*i);
605   }
606
607   // For each account known to chrome, PerformMergeAction() if the account is
608   // not already in the cookie jar or its state is invalid, or signal merge
609   // completed otherwise.  Make a copy of |add_to_cookie_| since calls to
610   // SignalComplete() will change the array.
611   std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
612   for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
613     if (gaia_accounts_.end() !=
614             std::find_if(gaia_accounts_.begin(),
615                          gaia_accounts_.end(),
616                          std::bind1st(EmailEqualToFunc(),
617                                       std::make_pair(add_to_cookie_copy[i],
618                                                      true)))) {
619       merge_session_helper_.SignalComplete(
620           add_to_cookie_copy[i],
621           GoogleServiceAuthError::AuthErrorNone());
622     } else {
623       PerformMergeAction(add_to_cookie_copy[i]);
624     }
625   }
626
627   // For each account in the gaia cookie not known to chrome,
628   // PerformAddToChromeAction.
629   for (std::vector<std::pair<std::string, int> >::const_iterator i =
630            add_to_chrome_.begin();
631        i != add_to_chrome_.end();
632        ++i) {
633     PerformAddToChromeAction(i->first, i->second);
634   }
635
636   CalculateIfReconcileIsDone();
637   ScheduleStartReconcileIfChromeAccountsChanged();
638 }
639
640 void AccountReconcilor::AbortReconcile() {
641   VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
642   DeleteFetchers();
643   add_to_cookie_.clear();
644   add_to_chrome_.clear();
645   CalculateIfReconcileIsDone();
646 }
647
648 void AccountReconcilor::CalculateIfReconcileIsDone() {
649   is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
650   if (!is_reconcile_started_)
651     VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
652 }
653
654 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
655   if (is_reconcile_started_)
656     return;
657
658   // Start a reconcile as the token accounts have changed.
659   VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
660   std::vector<std::string> reconciled_accounts(chrome_accounts_);
661   std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
662   std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
663   std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
664   if (reconciled_accounts != new_chrome_accounts) {
665     base::MessageLoop::current()->PostTask(
666         FROM_HERE,
667         base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
668   }
669 }
670
671 void AccountReconcilor::MergeSessionCompleted(
672     const std::string& account_id,
673     const GoogleServiceAuthError& error) {
674   VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
675           << account_id;
676
677   // Remove the account from the list that is being merged.
678   for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
679        i != add_to_cookie_.end();
680        ++i) {
681     if (account_id == *i) {
682       add_to_cookie_.erase(i);
683       break;
684     }
685   }
686
687   CalculateIfReconcileIsDone();
688   ScheduleStartReconcileIfChromeAccountsChanged();
689 }
690
691 void AccountReconcilor::HandleSuccessfulAccountIdCheck(
692     const std::string& account_id) {
693   valid_chrome_accounts_.insert(account_id);
694   FinishReconcile();
695 }
696
697 void AccountReconcilor::HandleFailedAccountIdCheck(
698     const std::string& account_id) {
699   invalid_chrome_accounts_.insert(account_id);
700   FinishReconcile();
701 }
702
703 void AccountReconcilor::HandleRefreshTokenFetched(
704     const std::string& account_id,
705     const std::string& refresh_token) {
706   if (!refresh_token.empty()) {
707     token_service_->UpdateCredentials(account_id, refresh_token);
708   }
709
710   // Remove the account from the list that is being updated.
711   for (std::vector<std::pair<std::string, int> >::iterator i =
712            add_to_chrome_.begin();
713        i != add_to_chrome_.end();
714        ++i) {
715     if (gaia::AreEmailsSame(account_id, i->first)) {
716       add_to_chrome_.erase(i);
717       break;
718     }
719   }
720
721   CalculateIfReconcileIsDone();
722 }