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