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 "base/logging.h" 6#include "base/time/time.h" 7#include "chrome/browser/chrome_notification_types.h" 8#include "chrome/browser/net/chrome_cookie_notification_details.h" 9#include "chrome/browser/profiles/profile.h" 10#include "chrome/browser/signin/account_reconcilor.h" 11#include "chrome/browser/signin/google_auto_login_helper.h" 12#include "chrome/browser/signin/profile_oauth2_token_service.h" 13#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 14#include "chrome/browser/signin/signin_manager.h" 15#include "chrome/browser/signin/signin_manager_factory.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/browser/notification_details.h" 18#include "content/public/browser/notification_source.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 23AccountReconcilor::AccountReconcilor(Profile* profile) 24 : profile_(profile), 25 are_gaia_accounts_set_(false), 26 requests_(NULL) { 27 DVLOG(1) << "AccountReconcilor::AccountReconcilor"; 28 RegisterWithSigninManager(); 29 RegisterWithCookieMonster(); 30 31 // If this profile is not connected, the reconcilor should do nothing but 32 // wait for the connection. 33 if (IsProfileConnected()) { 34 RegisterWithTokenService(); 35 StartPeriodicReconciliation(); 36 } 37} 38 39AccountReconcilor::~AccountReconcilor() { 40 // Make sure shutdown was called first. 41 DCHECK(registrar_.IsEmpty()); 42 DCHECK(!reconciliation_timer_.IsRunning()); 43 DCHECK(!requests_); 44} 45 46void AccountReconcilor::Shutdown() { 47 DVLOG(1) << "AccountReconcilor::Shutdown"; 48 DeleteAccessTokenRequests(); 49 UnregisterWithSigninManager(); 50 UnregisterWithTokenService(); 51 UnregisterWithCookieMonster(); 52 StopPeriodicReconciliation(); 53} 54 55void AccountReconcilor::DeleteAccessTokenRequests() { 56 delete[] requests_; 57 requests_ = NULL; 58} 59 60void AccountReconcilor::RegisterWithCookieMonster() { 61 content::Source<Profile> source(profile_); 62 registrar_.Add(this, chrome::NOTIFICATION_COOKIE_CHANGED, source); 63} 64 65void AccountReconcilor::UnregisterWithCookieMonster() { 66 content::Source<Profile> source(profile_); 67 registrar_.Remove(this, chrome::NOTIFICATION_COOKIE_CHANGED, source); 68} 69 70void AccountReconcilor::RegisterWithSigninManager() { 71 content::Source<Profile> source(profile_); 72 registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source); 73 registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source); 74} 75 76void AccountReconcilor::UnregisterWithSigninManager() { 77 content::Source<Profile> source(profile_); 78 registrar_.Remove( 79 this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source); 80 registrar_.Remove(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source); 81} 82 83void AccountReconcilor::RegisterWithTokenService() { 84 DVLOG(1) << "AccountReconcilor::RegisterWithTokenService"; 85 ProfileOAuth2TokenService* token_service = 86 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); 87 token_service->AddObserver(this); 88} 89 90void AccountReconcilor::UnregisterWithTokenService() { 91 ProfileOAuth2TokenService* token_service = 92 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); 93 token_service->RemoveObserver(this); 94} 95 96bool AccountReconcilor::IsProfileConnected() { 97 return !SigninManagerFactory::GetForProfile(profile_)-> 98 GetAuthenticatedUsername().empty(); 99} 100 101void AccountReconcilor::StartPeriodicReconciliation() { 102 DVLOG(1) << "AccountReconcilor::StartPeriodicReconciliation"; 103 // TODO(rogerta): pick appropriate thread and timeout value. 104 reconciliation_timer_.Start( 105 FROM_HERE, 106 base::TimeDelta::FromSeconds(300), 107 this, 108 &AccountReconcilor::PeriodicReconciliation); 109} 110 111void AccountReconcilor::StopPeriodicReconciliation() { 112 DVLOG(1) << "AccountReconcilor::StopPeriodicReconciliation"; 113 reconciliation_timer_.Stop(); 114} 115 116void AccountReconcilor::PeriodicReconciliation() { 117 DVLOG(1) << "AccountReconcilor::PeriodicReconciliation"; 118 StartReconcileAction(); 119} 120 121void AccountReconcilor::Observe(int type, 122 const content::NotificationSource& source, 123 const content::NotificationDetails& details) { 124 switch (type) { 125 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: 126 DVLOG(1) << "AccountReconcilor::Observe: signed in"; 127 RegisterWithTokenService(); 128 StartPeriodicReconciliation(); 129 break; 130 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: 131 DVLOG(1) << "AccountReconcilor::Observe: signed out"; 132 UnregisterWithTokenService(); 133 StopPeriodicReconciliation(); 134 break; 135 case chrome::NOTIFICATION_COOKIE_CHANGED: 136 OnCookieChanged(content::Details<ChromeCookieDetails>(details).ptr()); 137 break; 138 default: 139 NOTREACHED(); 140 break; 141 } 142} 143 144void AccountReconcilor::OnCookieChanged(ChromeCookieDetails* details) { 145 // TODO(acleung): Filter out cookies by looking at the domain. 146 // StartReconcileAction(); 147} 148 149void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) { 150 DVLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id; 151 PerformMergeAction(account_id); 152} 153 154void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) { 155 DVLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id; 156 PerformRemoveAction(account_id); 157} 158 159void AccountReconcilor::OnRefreshTokensLoaded() {} 160 161void AccountReconcilor::PerformMergeAction(const std::string& account_id) { 162 // GoogleAutoLoginHelper deletes itself upon success / failure. 163 GoogleAutoLoginHelper* helper = new GoogleAutoLoginHelper(profile_); 164 helper->LogIn(account_id); 165} 166 167void AccountReconcilor::PerformRemoveAction(const std::string& account_id) { 168 // TODO(acleung): Implement this: 169} 170 171void AccountReconcilor::StartReconcileAction() { 172 if (!IsProfileConnected()) 173 return; 174 175 // Reset state for validating gaia cookie. 176 are_gaia_accounts_set_ = false; 177 gaia_accounts_.clear(); 178 GetAccountsFromCookie(); 179 180 // Reset state for validating oauth2 tokens. 181 primary_account_.clear(); 182 chrome_accounts_.clear(); 183 DeleteAccessTokenRequests(); 184 valid_chrome_accounts_.clear(); 185 invalid_chrome_accounts_.clear(); 186 ValidateAccountsFromTokenService(); 187} 188 189void AccountReconcilor::GetAccountsFromCookie() { 190 gaia_fetcher_.reset(new GaiaAuthFetcher(this, GaiaConstants::kChromeSource, 191 profile_->GetRequestContext())); 192 gaia_fetcher_->StartListAccounts(); 193} 194 195void AccountReconcilor::OnListAccountsSuccess(const std::string& data) { 196 gaia_fetcher_.reset(); 197 198 // Get account information from response data. 199 gaia_accounts_ = gaia::ParseListAccountsData(data); 200 if (gaia_accounts_.size() > 0) { 201 DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: " 202 << "Gaia " << gaia_accounts_.size() << " accounts, " 203 << "Primary is '" << gaia_accounts_[0] << "'"; 204 } else { 205 DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts"; 206 } 207 208 are_gaia_accounts_set_ = true; 209 FinishReconcileAction(); 210} 211 212void AccountReconcilor::OnListAccountsFailure( 213 const GoogleServiceAuthError& error) { 214 gaia_fetcher_.reset(); 215 DVLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString(); 216 217 are_gaia_accounts_set_ = true; 218 FinishReconcileAction(); 219} 220 221void AccountReconcilor::ValidateAccountsFromTokenService() { 222 primary_account_ = 223 SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername(); 224 DCHECK(!primary_account_.empty()); 225 226 ProfileOAuth2TokenService* token_service = 227 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); 228 chrome_accounts_ = token_service->GetAccounts(); 229 DCHECK(chrome_accounts_.size() > 0); 230 231 DVLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " 232 << "Chrome " << chrome_accounts_.size() << " accounts, " 233 << "Primary is '" << primary_account_ << "'"; 234 235 DCHECK(!requests_); 236 requests_ = 237 new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()]; 238 for (size_t i = 0; i < chrome_accounts_.size(); ++i) { 239 requests_[i] = token_service->StartRequest(chrome_accounts_[i], 240 OAuth2TokenService::ScopeSet(), 241 this); 242 } 243} 244 245void AccountReconcilor::OnGetTokenSuccess( 246 const OAuth2TokenService::Request* request, 247 const std::string& access_token, 248 const base::Time& expiration_time) { 249 DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " 250 << request->GetAccountId(); 251 valid_chrome_accounts_.insert(request->GetAccountId()); 252 FinishReconcileAction(); 253} 254 255void AccountReconcilor::OnGetTokenFailure( 256 const OAuth2TokenService::Request* request, 257 const GoogleServiceAuthError& error) { 258 DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: invalid " 259 << request->GetAccountId(); 260 invalid_chrome_accounts_.insert(request->GetAccountId()); 261 FinishReconcileAction(); 262} 263 264void AccountReconcilor::FinishReconcileAction() { 265 // Make sure that the process of validating the gaia cookie and the oauth2 266 // tokens individually is done before proceeding with reconciliation. 267 if (!are_gaia_accounts_set_ || 268 (chrome_accounts_.size() != (valid_chrome_accounts_.size() + 269 invalid_chrome_accounts_.size()))) { 270 return; 271 } 272 273 DVLOG(1) << "AccountReconcilor::FinishReconcileAction"; 274 275 bool are_primaries_equal = 276 gaia_accounts_.size() > 0 && primary_account_ == gaia_accounts_[0]; 277 bool have_same_accounts = chrome_accounts_.size() == gaia_accounts_.size(); 278 if (have_same_accounts) { 279 for (size_t i = 0; i < gaia_accounts_.size(); ++i) { 280 if (std::find(chrome_accounts_.begin(), chrome_accounts_.end(), 281 gaia_accounts_[i]) == chrome_accounts_.end()) { 282 have_same_accounts = false; 283 break; 284 } 285 } 286 } 287 288 if (!are_primaries_equal || !have_same_accounts) { 289 // TODO(rogerta): fix things up. 290 } 291} 292