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