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