account_reconcilor.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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
29namespace {
30
31class 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
37bool 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.
49class 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
77AccountReconcilor::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
93void 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
108void 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
115bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
116                                                  const std::string& s2) const {
117  return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
118}
119
120class 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
144AccountReconcilor::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
159OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
160  OAuth2TokenService::ScopeSet scopes;
161  scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
162  return scopes;
163}
164
165void 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
173void 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
184void 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
194AccountReconcilor::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
212AccountReconcilor::~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
221void 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
239void 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
251void AccountReconcilor::AddMergeSessionObserver(
252    MergeSessionHelper::Observer* observer) {
253  merge_session_helper_.AddObserver(observer);
254}
255
256void AccountReconcilor::RemoveMergeSessionObserver(
257    MergeSessionHelper::Observer* observer) {
258  merge_session_helper_.RemoveObserver(observer);
259}
260
261void AccountReconcilor::DeleteFetchers() {
262  delete[] requests_;
263  requests_ = NULL;
264
265  user_id_fetchers_.clear();
266  refresh_token_fetchers_.clear();
267}
268
269bool AccountReconcilor::AreAllRefreshTokensChecked() const {
270  return chrome_accounts_.size() ==
271         (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
272}
273
274void 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  client_->SetCookieChangedCallback(
279      base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
280}
281
282void AccountReconcilor::UnregisterForCookieChanges() {
283  client_->SetCookieChangedCallback(SigninClient::CookieChangedCallback());
284}
285
286void AccountReconcilor::RegisterWithSigninManager() {
287  signin_manager_->AddObserver(this);
288}
289
290void AccountReconcilor::UnregisterWithSigninManager() {
291  signin_manager_->RemoveObserver(this);
292}
293
294void 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
306void AccountReconcilor::UnregisterWithTokenService() {
307  if (!registered_with_token_service_)
308    return;
309
310  token_service_->RemoveObserver(this);
311  registered_with_token_service_ = false;
312}
313
314bool AccountReconcilor::IsProfileConnected() {
315  return !signin_manager_->GetAuthenticatedUsername().empty();
316}
317
318void 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
335void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
336  VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
337  PerformStartRemoveAction(account_id);
338}
339
340void AccountReconcilor::OnEndBatchChanges() {
341  VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
342  StartReconcile();
343}
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}
351
352void 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
362void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
363  if (!switches::IsNewProfileManagement()) {
364    MarkAccountAsAddedToCookie(account_id);
365    return;
366  }
367  VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
368  merge_session_helper_.LogIn(account_id);
369}
370
371void 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
380void 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::IsNewProfileManagement())
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
402void AccountReconcilor::PerformAddToChromeAction(
403    const std::string& account_id,
404    int session_index,
405    const std::string& signin_scoped_device_id) {
406  if (!switches::IsNewProfileManagement()) {
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
419void AccountReconcilor::PerformLogoutAllAccountsAction() {
420  if (!switches::IsNewProfileManagement())
421    return;
422  VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
423  merge_session_helper_.LogOutAllAccounts();
424}
425
426void 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  // Reset state for validating gaia cookie.
435  are_gaia_accounts_set_ = false;
436  gaia_accounts_.clear();
437  GetAccountsFromCookie(base::Bind(
438      &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
439      base::Unretained(this)));
440
441  // Reset state for validating oauth2 tokens.
442  primary_account_.clear();
443  chrome_accounts_.clear();
444  DeleteFetchers();
445  valid_chrome_accounts_.clear();
446  invalid_chrome_accounts_.clear();
447  add_to_cookie_.clear();
448  add_to_chrome_.clear();
449  ValidateAccountsFromTokenService();
450}
451
452void AccountReconcilor::GetAccountsFromCookie(
453    GetAccountsFromCookieCallback callback) {
454  get_gaia_accounts_callbacks_.push_back(callback);
455  if (!gaia_fetcher_) {
456    // There is no list account request in flight.
457    gaia_fetcher_.reset(new GaiaAuthFetcher(
458        this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
459    gaia_fetcher_->StartListAccounts();
460  }
461}
462
463void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
464  gaia_fetcher_.reset();
465
466  // Get account information from response data.
467  std::vector<std::pair<std::string, bool> > gaia_accounts;
468  bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
469  if (!valid_json) {
470    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
471  } else if (gaia_accounts.size() > 0) {
472    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
473            << "Gaia " << gaia_accounts.size() << " accounts, "
474            << "Primary is '" << gaia_accounts[0].first << "'";
475  } else {
476    VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
477  }
478
479  // There must be at least one callback waiting for result.
480  DCHECK(!get_gaia_accounts_callbacks_.empty());
481
482  GoogleServiceAuthError error =
483      !valid_json ? GoogleServiceAuthError(
484                        GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
485                  : GoogleServiceAuthError::AuthErrorNone();
486  get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
487  get_gaia_accounts_callbacks_.pop_front();
488
489  MayBeDoNextListAccounts();
490}
491
492void AccountReconcilor::OnListAccountsFailure(
493    const GoogleServiceAuthError& error) {
494  gaia_fetcher_.reset();
495  VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
496  std::vector<std::pair<std::string, bool> > empty_accounts;
497
498  // There must be at least one callback waiting for result.
499  DCHECK(!get_gaia_accounts_callbacks_.empty());
500
501  get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
502  get_gaia_accounts_callbacks_.pop_front();
503
504  MayBeDoNextListAccounts();
505}
506
507void AccountReconcilor::MayBeDoNextListAccounts() {
508  if (!get_gaia_accounts_callbacks_.empty()) {
509    gaia_fetcher_.reset(new GaiaAuthFetcher(
510        this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
511    gaia_fetcher_->StartListAccounts();
512  }
513}
514
515void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
516    const GoogleServiceAuthError& error,
517    const std::vector<std::pair<std::string, bool> >& accounts) {
518  if (error.state() == GoogleServiceAuthError::NONE) {
519    gaia_accounts_ = accounts;
520    are_gaia_accounts_set_ = true;
521    FinishReconcile();
522  } else {
523    AbortReconcile();
524  }
525}
526
527void AccountReconcilor::ValidateAccountsFromTokenService() {
528  primary_account_ = signin_manager_->GetAuthenticatedUsername();
529  DCHECK(!primary_account_.empty());
530
531  chrome_accounts_ = token_service_->GetAccounts();
532
533  VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
534          << "Chrome " << chrome_accounts_.size() << " accounts, "
535          << "Primary is '" << primary_account_ << "'";
536
537  DCHECK(!requests_);
538  requests_ =
539      new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
540  const OAuth2TokenService::ScopeSet scopes =
541      AccountReconcilor::UserIdFetcher::GetScopes();
542  for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
543    requests_[i] =
544        token_service_->StartRequest(chrome_accounts_[i], scopes, this);
545  }
546
547  DCHECK_EQ(0u, user_id_fetchers_.size());
548  user_id_fetchers_.resize(chrome_accounts_.size());
549}
550
551void AccountReconcilor::OnGetTokenSuccess(
552    const OAuth2TokenService::Request* request,
553    const std::string& access_token,
554    const base::Time& expiration_time) {
555  size_t index;
556  for (index = 0; index < chrome_accounts_.size(); ++index) {
557    if (request == requests_[index].get())
558      break;
559  }
560  DCHECK(index < chrome_accounts_.size());
561
562  const std::string& account_id = chrome_accounts_[index];
563
564  VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
565
566  DCHECK(!user_id_fetchers_[index]);
567  user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
568}
569
570void AccountReconcilor::OnGetTokenFailure(
571    const OAuth2TokenService::Request* request,
572    const GoogleServiceAuthError& error) {
573  size_t index;
574  for (index = 0; index < chrome_accounts_.size(); ++index) {
575    if (request == requests_[index].get())
576      break;
577  }
578  DCHECK(index < chrome_accounts_.size());
579
580  const std::string& account_id = chrome_accounts_[index];
581
582  VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
583  HandleFailedAccountIdCheck(account_id);
584}
585
586void AccountReconcilor::OnNewProfileManagementFlagChanged(
587    bool new_flag_status) {
588  if (new_flag_status) {
589    // The reconciler may have been newly created just before this call, or may
590    // have already existed and in mid-reconcile. To err on the safe side, force
591    // a restart.
592    Shutdown();
593    Initialize(true);
594  } else {
595    Shutdown();
596  }
597}
598
599void AccountReconcilor::FinishReconcile() {
600  // Make sure that the process of validating the gaia cookie and the oauth2
601  // tokens individually is done before proceeding with reconciliation.
602  if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
603    return;
604
605  VLOG(1) << "AccountReconcilor::FinishReconcile";
606
607  DeleteFetchers();
608
609  DCHECK(add_to_cookie_.empty());
610  DCHECK(add_to_chrome_.empty());
611  int number_gaia_accounts = gaia_accounts_.size();
612  bool are_primaries_equal =
613      number_gaia_accounts > 0 &&
614      gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
615
616
617  if (are_primaries_equal) {
618    // Determine if we need to merge accounts from gaia cookie to chrome.
619    for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
620      const std::string& gaia_account = gaia_accounts_[i].first;
621      if (gaia_accounts_[i].second &&
622          valid_chrome_accounts_.find(gaia_account) ==
623              valid_chrome_accounts_.end()) {
624        add_to_chrome_.push_back(std::make_pair(gaia_account, i));
625      }
626    }
627  } else {
628    VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
629    // Really messed up state.  Blow away the gaia cookie completely and
630    // rebuild it, making sure the primary account as specified by the
631    // SigninManager is the first session in the gaia cookie.
632    PerformLogoutAllAccountsAction();
633    gaia_accounts_.clear();
634  }
635
636  // Create a list of accounts that need to be added to the gaia cookie.
637  // The primary account must be first to make sure it becomes the default
638  // account in the case where chrome is completely rebuilding the cookie.
639  add_to_cookie_.push_back(primary_account_);
640  for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
641        i != valid_chrome_accounts_.end();
642        ++i) {
643    if (*i != primary_account_)
644      add_to_cookie_.push_back(*i);
645  }
646
647  // For each account known to chrome, PerformMergeAction() if the account is
648  // not already in the cookie jar or its state is invalid, or signal merge
649  // completed otherwise.  Make a copy of |add_to_cookie_| since calls to
650  // SignalComplete() will change the array.
651  std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
652  int added_to_cookie = 0;
653  for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
654    if (gaia_accounts_.end() !=
655            std::find_if(gaia_accounts_.begin(),
656                         gaia_accounts_.end(),
657                         std::bind1st(EmailEqualToFunc(),
658                                      std::make_pair(add_to_cookie_copy[i],
659                                                     true)))) {
660      merge_session_helper_.SignalComplete(
661          add_to_cookie_copy[i],
662          GoogleServiceAuthError::AuthErrorNone());
663    } else {
664      PerformMergeAction(add_to_cookie_copy[i]);
665      added_to_cookie++;
666    }
667  }
668
669  std::string signin_scoped_device_id = client_->GetSigninScopedDeviceId();
670  // For each account in the gaia cookie not known to chrome,
671  // PerformAddToChromeAction. Make a copy of |add_to_chrome| since calls to
672  // PerformAddToChromeAction() may modify this array.
673  std::vector<std::pair<std::string, int> > add_to_chrome_copy = add_to_chrome_;
674  for (std::vector<std::pair<std::string, int> >::const_iterator i =
675           add_to_chrome_copy.begin();
676       i != add_to_chrome_copy.end();
677       ++i) {
678    PerformAddToChromeAction(i->first, i->second, signin_scoped_device_id);
679  }
680
681  signin_metrics::LogSigninAccountReconciliation(valid_chrome_accounts_.size(),
682                                                 added_to_cookie,
683                                                 add_to_chrome_.size(),
684                                                 are_primaries_equal,
685                                                 first_execution_,
686                                                 number_gaia_accounts);
687  first_execution_ = false;
688  CalculateIfReconcileIsDone();
689  ScheduleStartReconcileIfChromeAccountsChanged();
690}
691
692void AccountReconcilor::AbortReconcile() {
693  VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
694  DeleteFetchers();
695  add_to_cookie_.clear();
696  add_to_chrome_.clear();
697  CalculateIfReconcileIsDone();
698}
699
700void AccountReconcilor::CalculateIfReconcileIsDone() {
701  is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
702  if (!is_reconcile_started_)
703    VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
704}
705
706void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
707  if (is_reconcile_started_)
708    return;
709
710  // Start a reconcile as the token accounts have changed.
711  VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
712  std::vector<std::string> reconciled_accounts(chrome_accounts_);
713  std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
714  std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
715  std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
716  if (reconciled_accounts != new_chrome_accounts) {
717    base::MessageLoop::current()->PostTask(
718        FROM_HERE,
719        base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
720  }
721}
722
723// Remove the account from the list that is being merged.
724bool AccountReconcilor::MarkAccountAsAddedToCookie(
725    const std::string& account_id) {
726  for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
727       i != add_to_cookie_.end();
728       ++i) {
729    if (account_id == *i) {
730      add_to_cookie_.erase(i);
731      return true;
732    }
733  }
734  return false;
735}
736
737void AccountReconcilor::MergeSessionCompleted(
738    const std::string& account_id,
739    const GoogleServiceAuthError& error) {
740  VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
741          << account_id;
742
743  if (MarkAccountAsAddedToCookie(account_id)) {
744    CalculateIfReconcileIsDone();
745    ScheduleStartReconcileIfChromeAccountsChanged();
746  }
747}
748
749void AccountReconcilor::HandleSuccessfulAccountIdCheck(
750    const std::string& account_id) {
751  valid_chrome_accounts_.insert(account_id);
752  FinishReconcile();
753}
754
755void AccountReconcilor::HandleFailedAccountIdCheck(
756    const std::string& account_id) {
757  invalid_chrome_accounts_.insert(account_id);
758  FinishReconcile();
759}
760
761void AccountReconcilor::PerformAddAccountToTokenService(
762    const std::string& account_id,
763    const std::string& refresh_token) {
764  // The flow should never get to this method if new_profile_management is
765  // false, but better safe than sorry.
766  if (!switches::IsNewProfileManagement())
767    return;
768  token_service_->UpdateCredentials(account_id, refresh_token);
769}
770
771// Remove the account from the list that is being updated.
772void AccountReconcilor::MarkAccountAsAddedToChrome(
773    const std::string& account_id) {
774  for (std::vector<std::pair<std::string, int> >::iterator i =
775           add_to_chrome_.begin();
776       i != add_to_chrome_.end();
777       ++i) {
778    if (gaia::AreEmailsSame(account_id, i->first)) {
779      add_to_chrome_.erase(i);
780      break;
781    }
782  }
783}
784
785void AccountReconcilor::HandleRefreshTokenFetched(
786    const std::string& account_id,
787    const std::string& refresh_token) {
788  if (!refresh_token.empty())
789    PerformAddAccountToTokenService(account_id, refresh_token);
790
791  MarkAccountAsAddedToChrome(account_id);
792  CalculateIfReconcileIsDone();
793}
794