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