Upstream version 9.38.198.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_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"
27
28
29 namespace {
30
31 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
32  public:
33   bool operator()(const std::pair<std::string, bool>& p1,
34                   const std::pair<std::string, bool>& p2) const;
35 };
36
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);
41 }
42
43 }  // namespace
44
45
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
48 // shortly anyway.
49 class AccountReconcilor::RefreshTokenFetcher
50     : public SigninOAuthHelper,
51       public SigninOAuthHelper::Consumer {
52  public:
53   RefreshTokenFetcher(AccountReconcilor* reconcilor,
54                       const std::string& account_id,
55                       int session_index,
56                       const std::string& signin_scoped_device_id);
57   virtual ~RefreshTokenFetcher() {}
58
59  private:
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;
65
66   // Called when an error occurs while getting the information.
67   virtual void OnSigninOAuthInformationFailure(
68       const GoogleServiceAuthError& error) OVERRIDE;
69
70   AccountReconcilor* reconcilor_;
71   const std::string account_id_;
72   int session_index_;
73
74   DISALLOW_COPY_AND_ASSIGN(RefreshTokenFetcher);
75 };
76
77 AccountReconcilor::RefreshTokenFetcher::RefreshTokenFetcher(
78     AccountReconcilor* reconcilor,
79     const std::string& account_id,
80     int session_index,
81     const std::string& signin_scoped_device_id)
82     : SigninOAuthHelper(reconcilor->client()->GetURLRequestContext(),
83                         base::IntToString(session_index),
84                         signin_scoped_device_id,
85                         this),
86       reconcilor_(reconcilor),
87       account_id_(account_id),
88       session_index_(session_index) {
89   DCHECK(reconcilor_);
90   DCHECK(!account_id.empty());
91 }
92
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;
100
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);
106 }
107
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());
113 }
114
115 bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
116                                                   const std::string& s2) const {
117   return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
118 }
119
120 class AccountReconcilor::UserIdFetcher
121     : public gaia::GaiaOAuthClient::Delegate {
122  public:
123   UserIdFetcher(AccountReconcilor* reconcilor,
124                 const std::string& access_token,
125                 const std::string& account_id);
126
127   // Returns the scopes needed by the UserIdFetcher.
128   static OAuth2TokenService::ScopeSet GetScopes();
129
130  private:
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;
135
136   AccountReconcilor* const reconcilor_;
137   const std::string account_id_;
138   const std::string access_token_;
139   gaia::GaiaOAuthClient gaia_auth_client_;
140
141   DISALLOW_COPY_AND_ASSIGN(UserIdFetcher);
142 };
143
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()) {
151   DCHECK(reconcilor_);
152   DCHECK(!account_id_.empty());
153
154   const int kMaxRetries = 5;
155   gaia_auth_client_.GetUserId(access_token_, kMaxRetries, this);
156 }
157
158 // static
159 OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
160   OAuth2TokenService::ScopeSet scopes;
161   scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
162   return scopes;
163 }
164
165 void AccountReconcilor::UserIdFetcher::OnGetUserIdResponse(
166     const std::string& user_id) {
167   VLOG(1) << "AccountReconcilor::OnGetUserIdResponse: " << account_id_;
168
169   // HandleSuccessfulAccountIdCheck() may delete |this|, so call it last.
170   reconcilor_->HandleSuccessfulAccountIdCheck(account_id_);
171 }
172
173 void AccountReconcilor::UserIdFetcher::OnOAuthError() {
174   VLOG(1) << "AccountReconcilor::OnOAuthError: " << account_id_;
175
176   // Invalidate the access token to force a refetch next time.
177   reconcilor_->token_service()->InvalidateToken(
178       account_id_, GetScopes(), access_token_);
179
180   // HandleFailedAccountIdCheck() may delete |this|, so call it last.
181   reconcilor_->HandleFailedAccountIdCheck(account_id_);
182 }
183
184 void AccountReconcilor::UserIdFetcher::OnNetworkError(int response_code) {
185   VLOG(1) << "AccountReconcilor::OnNetworkError: " << account_id_
186           << " response_code=" << response_code;
187
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_);
192 }
193
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),
200       client_(client),
201       merge_session_helper_(token_service_,
202                             client->GetURLRequestContext(),
203                             this),
204       registered_with_token_service_(false),
205       is_reconcile_started_(false),
206       first_execution_(true),
207       are_gaia_accounts_set_(false),
208       requests_(NULL) {
209   VLOG(1) << "AccountReconcilor::AccountReconcilor";
210 }
211
212 AccountReconcilor::~AccountReconcilor() {
213   VLOG(1) << "AccountReconcilor::~AccountReconcilor";
214   // Make sure shutdown was called first.
215   DCHECK(!registered_with_token_service_);
216   DCHECK(!requests_);
217   DCHECK_EQ(0u, user_id_fetchers_.size());
218   DCHECK_EQ(0u, refresh_token_fetchers_.size());
219 }
220
221 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
222   VLOG(1) << "AccountReconcilor::Initialize";
223   RegisterWithSigninManager();
224
225   // If this user is not signed in, the reconcilor should do nothing but
226   // wait for signin.
227   if (IsProfileConnected()) {
228     RegisterForCookieChanges();
229     RegisterWithTokenService();
230
231     // Start a reconcile if the tokens are already loaded.
232     if (start_reconcile_if_tokens_available &&
233         token_service_->GetAccounts().size() > 0) {
234       StartReconcile();
235     }
236   }
237 }
238
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();
245   DeleteFetchers();
246   UnregisterWithSigninManager();
247   UnregisterWithTokenService();
248   UnregisterForCookieChanges();
249 }
250
251 void AccountReconcilor::AddMergeSessionObserver(
252     MergeSessionHelper::Observer* observer) {
253   merge_session_helper_.AddObserver(observer);
254 }
255
256 void AccountReconcilor::RemoveMergeSessionObserver(
257     MergeSessionHelper::Observer* observer) {
258   merge_session_helper_.RemoveObserver(observer);
259 }
260
261 void AccountReconcilor::DeleteFetchers() {
262   delete[] requests_;
263   requests_ = NULL;
264
265   user_id_fetchers_.clear();
266   refresh_token_fetchers_.clear();
267 }
268
269 bool AccountReconcilor::AreAllRefreshTokensChecked() const {
270   return chrome_accounts_.size() ==
271          (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
272 }
273
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)));
280 }
281
282 void AccountReconcilor::UnregisterForCookieChanges() {
283   cookie_changed_subscription_.reset();
284 }
285
286 void AccountReconcilor::RegisterWithSigninManager() {
287   signin_manager_->AddObserver(this);
288 }
289
290 void AccountReconcilor::UnregisterWithSigninManager() {
291   signin_manager_->RemoveObserver(this);
292 }
293
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_)
300     return;
301
302   token_service_->AddObserver(this);
303   registered_with_token_service_ = true;
304 }
305
306 void AccountReconcilor::UnregisterWithTokenService() {
307   if (!registered_with_token_service_)
308     return;
309
310   token_service_->RemoveObserver(this);
311   registered_with_token_service_ = false;
312 }
313
314 bool AccountReconcilor::IsProfileConnected() {
315   return !signin_manager_->GetAuthenticatedUsername().empty();
316 }
317
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";
323
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.";
328       return;
329     }
330
331     StartReconcile();
332   }
333 }
334
335 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
336   VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
337   PerformStartRemoveAction(account_id);
338 }
339
340 void AccountReconcilor::OnEndBatchChanges() {
341   VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
342   StartReconcile();
343 }
344
345 void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
346                                               const std::string& password) {
347   VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
348   RegisterForCookieChanges();
349   RegisterWithTokenService();
350 }
351
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();
356   AbortReconcile();
357   UnregisterWithTokenService();
358   UnregisterForCookieChanges();
359   PerformLogoutAllAccountsAction();
360 }
361
362 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
363   if (!switches::IsEnableAccountConsistency()) {
364     MarkAccountAsAddedToCookie(account_id);
365     return;
366   }
367   VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
368   merge_session_helper_.LogIn(account_id);
369 }
370
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),
377       account_id));
378 }
379
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())
385     return;
386   VLOG(1) << "AccountReconcilor::PerformFinishRemoveAction:"
387           << " account=" << account_id << " error=" << error.ToString();
388   if (error.state() == GoogleServiceAuthError::NONE) {
389     AbortReconcile();
390     std::vector<std::string> accounts_only;
391     for (std::vector<std::pair<std::string, bool> >::const_iterator i =
392              accounts.begin();
393          i != accounts.end();
394          ++i) {
395       accounts_only.push_back(i->first);
396     }
397     merge_session_helper_.LogOut(account_id, accounts_only);
398   }
399   // Wait for the next ReconcileAction if there is an error.
400 }
401
402 void AccountReconcilor::PerformAddToChromeAction(
403     const std::string& account_id,
404     int session_index,
405     const std::string& signin_scoped_device_id) {
406   if (!switches::IsEnableAccountConsistency()) {
407     MarkAccountAsAddedToChrome(account_id);
408     return;
409   }
410   VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
411           << " account=" << account_id << " session_index=" << session_index;
412
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));
416 #endif
417 }
418
419 void AccountReconcilor::PerformLogoutAllAccountsAction() {
420   if (!switches::IsEnableAccountConsistency())
421     return;
422   VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
423   merge_session_helper_.LogOutAllAccounts();
424 }
425
426 void AccountReconcilor::StartReconcile() {
427   if (!IsProfileConnected() || is_reconcile_started_ ||
428       get_gaia_accounts_callbacks_.size() > 0 ||
429       merge_session_helper_.is_running())
430     return;
431
432   is_reconcile_started_ = true;
433
434   StartFetchingExternalCcResult();
435
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)));
442
443   // Reset state for validating oauth2 tokens.
444   primary_account_.clear();
445   chrome_accounts_.clear();
446   DeleteFetchers();
447   valid_chrome_accounts_.clear();
448   invalid_chrome_accounts_.clear();
449   add_to_cookie_.clear();
450   add_to_chrome_.clear();
451   ValidateAccountsFromTokenService();
452 }
453
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();
462   }
463 }
464
465 void AccountReconcilor::StartFetchingExternalCcResult() {
466   merge_session_helper_.StartFetchingExternalCcResult();
467 }
468
469 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
470   gaia_fetcher_.reset();
471
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);
475   if (!valid_json) {
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 << "'";
481   } else {
482     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
483   }
484
485   // There must be at least one callback waiting for result.
486   DCHECK(!get_gaia_accounts_callbacks_.empty());
487
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();
494
495   MayBeDoNextListAccounts();
496 }
497
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;
503
504   // There must be at least one callback waiting for result.
505   DCHECK(!get_gaia_accounts_callbacks_.empty());
506
507   get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
508   get_gaia_accounts_callbacks_.pop_front();
509
510   MayBeDoNextListAccounts();
511 }
512
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();
518   }
519 }
520
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;
527     FinishReconcile();
528   } else {
529     AbortReconcile();
530   }
531 }
532
533 void AccountReconcilor::ValidateAccountsFromTokenService() {
534   primary_account_ = signin_manager_->GetAuthenticatedUsername();
535   DCHECK(!primary_account_.empty());
536
537   chrome_accounts_ = token_service_->GetAccounts();
538
539   VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
540           << "Chrome " << chrome_accounts_.size() << " accounts, "
541           << "Primary is '" << primary_account_ << "'";
542
543   DCHECK(!requests_);
544   requests_ =
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) {
549     requests_[i] =
550         token_service_->StartRequest(chrome_accounts_[i], scopes, this);
551   }
552
553   DCHECK_EQ(0u, user_id_fetchers_.size());
554   user_id_fetchers_.resize(chrome_accounts_.size());
555 }
556
557 void AccountReconcilor::OnGetTokenSuccess(
558     const OAuth2TokenService::Request* request,
559     const std::string& access_token,
560     const base::Time& expiration_time) {
561   size_t index;
562   for (index = 0; index < chrome_accounts_.size(); ++index) {
563     if (request == requests_[index].get())
564       break;
565   }
566   DCHECK(index < chrome_accounts_.size());
567
568   const std::string& account_id = chrome_accounts_[index];
569
570   VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
571
572   DCHECK(!user_id_fetchers_[index]);
573   user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
574 }
575
576 void AccountReconcilor::OnGetTokenFailure(
577     const OAuth2TokenService::Request* request,
578     const GoogleServiceAuthError& error) {
579   size_t index;
580   for (index = 0; index < chrome_accounts_.size(); ++index) {
581     if (request == requests_[index].get())
582       break;
583   }
584   DCHECK(index < chrome_accounts_.size());
585
586   const std::string& account_id = chrome_accounts_[index];
587
588   VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
589   HandleFailedAccountIdCheck(account_id);
590 }
591
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
597     // a restart.
598     Shutdown();
599     Initialize(true);
600   } else {
601     Shutdown();
602   }
603 }
604
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())
609     return;
610
611   VLOG(1) << "AccountReconcilor::FinishReconcile";
612
613   DeleteFetchers();
614
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);
621
622
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));
631       }
632     }
633   } else {
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();
640   }
641
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();
648         ++i) {
649     if (*i != primary_account_)
650       add_to_cookie_.push_back(*i);
651   }
652
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],
667                                                      true)))) {
668       merge_session_helper_.SignalComplete(
669           add_to_cookie_copy[i],
670           GoogleServiceAuthError::AuthErrorNone());
671     } else {
672       PerformMergeAction(add_to_cookie_copy[i]);
673       added_to_cookie++;
674     }
675   }
676
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);
681
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();
690        ++i) {
691     PerformAddToChromeAction(i->first, i->second, signin_scoped_device_id);
692   }
693
694   signin_metrics::LogSigninAccountReconciliation(valid_chrome_accounts_.size(),
695                                                  added_to_cookie,
696                                                  add_to_chrome_.size(),
697                                                  are_primaries_equal,
698                                                  first_execution_,
699                                                  number_gaia_accounts);
700   first_execution_ = false;
701   CalculateIfReconcileIsDone();
702   ScheduleStartReconcileIfChromeAccountsChanged();
703 }
704
705 void AccountReconcilor::AbortReconcile() {
706   VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
707   DeleteFetchers();
708   add_to_cookie_.clear();
709   add_to_chrome_.clear();
710   CalculateIfReconcileIsDone();
711 }
712
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";
717 }
718
719 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
720   if (is_reconcile_started_)
721     return;
722
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(
731         FROM_HERE,
732         base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
733   }
734 }
735
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();
741        ++i) {
742     if (account_id == *i) {
743       add_to_cookie_.erase(i);
744       return true;
745     }
746   }
747   return false;
748 }
749
750 void AccountReconcilor::MergeSessionCompleted(
751     const std::string& account_id,
752     const GoogleServiceAuthError& error) {
753   VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
754           << account_id;
755
756   if (MarkAccountAsAddedToCookie(account_id)) {
757     CalculateIfReconcileIsDone();
758     ScheduleStartReconcileIfChromeAccountsChanged();
759   }
760 }
761
762 void AccountReconcilor::HandleSuccessfulAccountIdCheck(
763     const std::string& account_id) {
764   valid_chrome_accounts_.insert(account_id);
765   FinishReconcile();
766 }
767
768 void AccountReconcilor::HandleFailedAccountIdCheck(
769     const std::string& account_id) {
770   invalid_chrome_accounts_.insert(account_id);
771   FinishReconcile();
772 }
773
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())
780     return;
781   token_service_->UpdateCredentials(account_id, refresh_token);
782 }
783
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();
790        ++i) {
791     if (gaia::AreEmailsSame(account_id, i->first)) {
792       add_to_chrome_.erase(i);
793       break;
794     }
795   }
796 }
797
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);
803
804   MarkAccountAsAddedToChrome(account_id);
805   CalculateIfReconcileIsDone();
806 }