// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/logging.h" #include "base/time/time.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/net/chrome_cookie_notification_details.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/account_reconcilor.h" #include "chrome/browser/signin/google_auto_login_helper.h" #include "chrome/browser/signin/profile_oauth2_token_service.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "google_apis/gaia/gaia_auth_fetcher.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_constants.h" AccountReconcilor::AccountReconcilor(Profile* profile) : profile_(profile), are_gaia_accounts_set_(false), requests_(NULL) { DVLOG(1) << "AccountReconcilor::AccountReconcilor"; RegisterWithSigninManager(); RegisterWithCookieMonster(); // If this profile is not connected, the reconcilor should do nothing but // wait for the connection. if (IsProfileConnected()) { RegisterWithTokenService(); StartPeriodicReconciliation(); } } AccountReconcilor::~AccountReconcilor() { // Make sure shutdown was called first. DCHECK(registrar_.IsEmpty()); DCHECK(!reconciliation_timer_.IsRunning()); DCHECK(!requests_); } void AccountReconcilor::Shutdown() { DVLOG(1) << "AccountReconcilor::Shutdown"; DeleteAccessTokenRequests(); UnregisterWithSigninManager(); UnregisterWithTokenService(); UnregisterWithCookieMonster(); StopPeriodicReconciliation(); } void AccountReconcilor::DeleteAccessTokenRequests() { delete[] requests_; requests_ = NULL; } void AccountReconcilor::RegisterWithCookieMonster() { content::Source<Profile> source(profile_); registrar_.Add(this, chrome::NOTIFICATION_COOKIE_CHANGED, source); } void AccountReconcilor::UnregisterWithCookieMonster() { content::Source<Profile> source(profile_); registrar_.Remove(this, chrome::NOTIFICATION_COOKIE_CHANGED, source); } void AccountReconcilor::RegisterWithSigninManager() { content::Source<Profile> source(profile_); registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source); registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source); } void AccountReconcilor::UnregisterWithSigninManager() { content::Source<Profile> source(profile_); registrar_.Remove( this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source); registrar_.Remove(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source); } void AccountReconcilor::RegisterWithTokenService() { DVLOG(1) << "AccountReconcilor::RegisterWithTokenService"; ProfileOAuth2TokenService* token_service = ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); token_service->AddObserver(this); } void AccountReconcilor::UnregisterWithTokenService() { ProfileOAuth2TokenService* token_service = ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); token_service->RemoveObserver(this); } bool AccountReconcilor::IsProfileConnected() { return !SigninManagerFactory::GetForProfile(profile_)-> GetAuthenticatedUsername().empty(); } void AccountReconcilor::StartPeriodicReconciliation() { DVLOG(1) << "AccountReconcilor::StartPeriodicReconciliation"; // TODO(rogerta): pick appropriate thread and timeout value. reconciliation_timer_.Start( FROM_HERE, base::TimeDelta::FromSeconds(300), this, &AccountReconcilor::PeriodicReconciliation); } void AccountReconcilor::StopPeriodicReconciliation() { DVLOG(1) << "AccountReconcilor::StopPeriodicReconciliation"; reconciliation_timer_.Stop(); } void AccountReconcilor::PeriodicReconciliation() { DVLOG(1) << "AccountReconcilor::PeriodicReconciliation"; StartReconcileAction(); } void AccountReconcilor::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: DVLOG(1) << "AccountReconcilor::Observe: signed in"; RegisterWithTokenService(); StartPeriodicReconciliation(); break; case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: DVLOG(1) << "AccountReconcilor::Observe: signed out"; UnregisterWithTokenService(); StopPeriodicReconciliation(); break; case chrome::NOTIFICATION_COOKIE_CHANGED: OnCookieChanged(content::Details<ChromeCookieDetails>(details).ptr()); break; default: NOTREACHED(); break; } } void AccountReconcilor::OnCookieChanged(ChromeCookieDetails* details) { // TODO(acleung): Filter out cookies by looking at the domain. // StartReconcileAction(); } void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) { DVLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id; PerformMergeAction(account_id); } void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) { DVLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id; PerformRemoveAction(account_id); } void AccountReconcilor::OnRefreshTokensLoaded() {} void AccountReconcilor::PerformMergeAction(const std::string& account_id) { // GoogleAutoLoginHelper deletes itself upon success / failure. GoogleAutoLoginHelper* helper = new GoogleAutoLoginHelper(profile_); helper->LogIn(account_id); } void AccountReconcilor::PerformRemoveAction(const std::string& account_id) { // TODO(acleung): Implement this: } void AccountReconcilor::StartReconcileAction() { if (!IsProfileConnected()) return; // Reset state for validating gaia cookie. are_gaia_accounts_set_ = false; gaia_accounts_.clear(); GetAccountsFromCookie(); // Reset state for validating oauth2 tokens. primary_account_.clear(); chrome_accounts_.clear(); DeleteAccessTokenRequests(); valid_chrome_accounts_.clear(); invalid_chrome_accounts_.clear(); ValidateAccountsFromTokenService(); } void AccountReconcilor::GetAccountsFromCookie() { gaia_fetcher_.reset(new GaiaAuthFetcher(this, GaiaConstants::kChromeSource, profile_->GetRequestContext())); gaia_fetcher_->StartListAccounts(); } void AccountReconcilor::OnListAccountsSuccess(const std::string& data) { gaia_fetcher_.reset(); // Get account information from response data. gaia_accounts_ = gaia::ParseListAccountsData(data); if (gaia_accounts_.size() > 0) { DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: " << "Gaia " << gaia_accounts_.size() << " accounts, " << "Primary is '" << gaia_accounts_[0] << "'"; } else { DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts"; } are_gaia_accounts_set_ = true; FinishReconcileAction(); } void AccountReconcilor::OnListAccountsFailure( const GoogleServiceAuthError& error) { gaia_fetcher_.reset(); DVLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString(); are_gaia_accounts_set_ = true; FinishReconcileAction(); } void AccountReconcilor::ValidateAccountsFromTokenService() { primary_account_ = SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername(); DCHECK(!primary_account_.empty()); ProfileOAuth2TokenService* token_service = ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); chrome_accounts_ = token_service->GetAccounts(); DCHECK(chrome_accounts_.size() > 0); DVLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " << "Chrome " << chrome_accounts_.size() << " accounts, " << "Primary is '" << primary_account_ << "'"; DCHECK(!requests_); requests_ = new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()]; for (size_t i = 0; i < chrome_accounts_.size(); ++i) { requests_[i] = token_service->StartRequest(chrome_accounts_[i], OAuth2TokenService::ScopeSet(), this); } } void AccountReconcilor::OnGetTokenSuccess( const OAuth2TokenService::Request* request, const std::string& access_token, const base::Time& expiration_time) { DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << request->GetAccountId(); valid_chrome_accounts_.insert(request->GetAccountId()); FinishReconcileAction(); } void AccountReconcilor::OnGetTokenFailure( const OAuth2TokenService::Request* request, const GoogleServiceAuthError& error) { DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: invalid " << request->GetAccountId(); invalid_chrome_accounts_.insert(request->GetAccountId()); FinishReconcileAction(); } void AccountReconcilor::FinishReconcileAction() { // Make sure that the process of validating the gaia cookie and the oauth2 // tokens individually is done before proceeding with reconciliation. if (!are_gaia_accounts_set_ || (chrome_accounts_.size() != (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size()))) { return; } DVLOG(1) << "AccountReconcilor::FinishReconcileAction"; bool are_primaries_equal = gaia_accounts_.size() > 0 && primary_account_ == gaia_accounts_[0]; bool have_same_accounts = chrome_accounts_.size() == gaia_accounts_.size(); if (have_same_accounts) { for (size_t i = 0; i < gaia_accounts_.size(); ++i) { if (std::find(chrome_accounts_.begin(), chrome_accounts_.end(), gaia_accounts_[i]) == chrome_accounts_.end()) { have_same_accounts = false; break; } } } if (!are_primaries_equal || !have_same_accounts) { // TODO(rogerta): fix things up. } }