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