1 // Copyright (c) 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/invalidation/ticl_invalidation_service.h" 6 7 #include "base/command_line.h" 8 #include "base/metrics/histogram.h" 9 #include "chrome/browser/chrome_notification_types.h" 10 #include "chrome/browser/invalidation/invalidation_service_util.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/signin/about_signin_internals.h" 13 #include "chrome/browser/signin/about_signin_internals_factory.h" 14 #include "chrome/browser/signin/profile_oauth2_token_service.h" 15 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 16 #include "chrome/browser/signin/signin_manager.h" 17 #include "content/public/browser/notification_service.h" 18 #include "google_apis/gaia/gaia_constants.h" 19 #include "sync/notifier/invalidator.h" 20 #include "sync/notifier/invalidator_state.h" 21 #include "sync/notifier/non_blocking_invalidator.h" 22 23 static const char* kOAuth2Scopes[] = { 24 GaiaConstants::kGoogleTalkOAuth2Scope 25 }; 26 27 static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = { 28 // Number of initial errors (in sequence) to ignore before applying 29 // exponential back-off rules. 30 0, 31 32 // Initial delay for exponential back-off in ms. 33 2000, 34 35 // Factor by which the waiting time will be multiplied. 36 2, 37 38 // Fuzzing percentage. ex: 10% will spread requests randomly 39 // between 90%-100% of the calculated time. 40 0.2, // 20% 41 42 // Maximum amount of time we are willing to delay our request in ms. 43 // TODO(pavely): crbug.com/246686 ProfileSyncService should retry 44 // RequestAccessToken on connection state change after backoff 45 1000 * 3600 * 4, // 4 hours. 46 47 // Time to keep an entry from being discarded even when it 48 // has no significant state, -1 to never discard. 49 -1, 50 51 // Don't use initial delay unless the last request was an error. 52 false, 53 }; 54 55 namespace invalidation { 56 57 TiclInvalidationService::TiclInvalidationService( 58 SigninManagerBase* signin, 59 ProfileOAuth2TokenService* oauth2_token_service, 60 Profile* profile) 61 : profile_(profile), 62 signin_manager_(signin), 63 oauth2_token_service_(oauth2_token_service), 64 invalidator_registrar_(new syncer::InvalidatorRegistrar()), 65 request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy) { 66 } 67 68 TiclInvalidationService::~TiclInvalidationService() { 69 DCHECK(CalledOnValidThread()); 70 } 71 72 void TiclInvalidationService::Init() { 73 DCHECK(CalledOnValidThread()); 74 75 invalidator_storage_.reset(new InvalidatorStorage(profile_->GetPrefs())); 76 if (invalidator_storage_->GetInvalidatorClientId().empty()) { 77 // This also clears any existing state. We can't reuse old invalidator 78 // state with the new ID anyway. 79 invalidator_storage_->SetInvalidatorClientId(GenerateInvalidatorClientId()); 80 } 81 82 if (IsReadyToStart()) { 83 StartInvalidator(); 84 } 85 86 notification_registrar_.Add(this, 87 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, 88 content::Source<Profile>(profile_)); 89 oauth2_token_service_->AddObserver(this); 90 } 91 92 void TiclInvalidationService::InitForTest(syncer::Invalidator* invalidator) { 93 // Here we perform the equivalent of Init() and StartInvalidator(), but with 94 // some minor changes to account for the fact that we're injecting the 95 // invalidator. 96 invalidator_.reset(invalidator); 97 98 invalidator_->RegisterHandler(this); 99 invalidator_->UpdateRegisteredIds( 100 this, 101 invalidator_registrar_->GetAllRegisteredIds()); 102 } 103 104 void TiclInvalidationService::RegisterInvalidationHandler( 105 syncer::InvalidationHandler* handler) { 106 DCHECK(CalledOnValidThread()); 107 DVLOG(2) << "Registering an invalidation handler"; 108 invalidator_registrar_->RegisterHandler(handler); 109 } 110 111 void TiclInvalidationService::UpdateRegisteredInvalidationIds( 112 syncer::InvalidationHandler* handler, 113 const syncer::ObjectIdSet& ids) { 114 DCHECK(CalledOnValidThread()); 115 DVLOG(2) << "Registering ids: " << ids.size(); 116 invalidator_registrar_->UpdateRegisteredIds(handler, ids); 117 if (invalidator_) { 118 invalidator_->UpdateRegisteredIds( 119 this, 120 invalidator_registrar_->GetAllRegisteredIds()); 121 } 122 } 123 124 void TiclInvalidationService::UnregisterInvalidationHandler( 125 syncer::InvalidationHandler* handler) { 126 DCHECK(CalledOnValidThread()); 127 DVLOG(2) << "Unregistering"; 128 invalidator_registrar_->UnregisterHandler(handler); 129 if (invalidator_) { 130 invalidator_->UpdateRegisteredIds( 131 this, 132 invalidator_registrar_->GetAllRegisteredIds()); 133 } 134 } 135 136 syncer::InvalidatorState TiclInvalidationService::GetInvalidatorState() const { 137 DCHECK(CalledOnValidThread()); 138 if (invalidator_) { 139 DVLOG(2) << "GetInvalidatorState returning " 140 << invalidator_->GetInvalidatorState(); 141 return invalidator_->GetInvalidatorState(); 142 } else { 143 DVLOG(2) << "Invalidator currently stopped"; 144 return syncer::TRANSIENT_INVALIDATION_ERROR; 145 } 146 } 147 148 std::string TiclInvalidationService::GetInvalidatorClientId() const { 149 DCHECK(CalledOnValidThread()); 150 return invalidator_storage_->GetInvalidatorClientId(); 151 } 152 153 void TiclInvalidationService::Observe( 154 int type, 155 const content::NotificationSource& source, 156 const content::NotificationDetails& details) { 157 DCHECK(CalledOnValidThread()); 158 DCHECK_EQ(type, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT); 159 Logout(); 160 } 161 162 void TiclInvalidationService::RequestAccessToken() { 163 // Only one active request at a time. 164 if (access_token_request_ != NULL) 165 return; 166 request_access_token_retry_timer_.Stop(); 167 OAuth2TokenService::ScopeSet oauth2_scopes; 168 for (size_t i = 0; i < arraysize(kOAuth2Scopes); i++) 169 oauth2_scopes.insert(kOAuth2Scopes[i]); 170 // Invalidate previous token, otherwise token service will return the same 171 // token again. 172 const std::string& account_id = oauth2_token_service_->GetPrimaryAccountId(); 173 oauth2_token_service_->InvalidateToken(account_id, 174 oauth2_scopes, 175 access_token_); 176 access_token_.clear(); 177 access_token_request_ = oauth2_token_service_->StartRequest(account_id, 178 oauth2_scopes, 179 this); 180 } 181 182 void TiclInvalidationService::OnGetTokenSuccess( 183 const OAuth2TokenService::Request* request, 184 const std::string& access_token, 185 const base::Time& expiration_time) { 186 DCHECK_EQ(access_token_request_, request); 187 access_token_request_.reset(); 188 // Reset backoff time after successful response. 189 request_access_token_backoff_.Reset(); 190 access_token_ = access_token; 191 if (!IsStarted() && IsReadyToStart()) { 192 StartInvalidator(); 193 } else { 194 UpdateInvalidatorCredentials(); 195 } 196 } 197 198 void TiclInvalidationService::OnGetTokenFailure( 199 const OAuth2TokenService::Request* request, 200 const GoogleServiceAuthError& error) { 201 DCHECK_EQ(access_token_request_, request); 202 DCHECK_NE(error.state(), GoogleServiceAuthError::NONE); 203 access_token_request_.reset(); 204 switch (error.state()) { 205 case GoogleServiceAuthError::CONNECTION_FAILED: 206 case GoogleServiceAuthError::SERVICE_UNAVAILABLE: { 207 // Transient error. Retry after some time. 208 request_access_token_backoff_.InformOfRequest(false); 209 request_access_token_retry_timer_.Start( 210 FROM_HERE, 211 request_access_token_backoff_.GetTimeUntilRelease(), 212 base::Bind(&TiclInvalidationService::RequestAccessToken, 213 base::Unretained(this))); 214 break; 215 } 216 case GoogleServiceAuthError::SERVICE_ERROR: 217 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: { 218 // This is a real auth error. 219 // Report time since token was issued for invalid credentials error. 220 base::Time auth_token_time = 221 AboutSigninInternalsFactory::GetForProfile(profile_)-> 222 GetTokenTime(GaiaConstants::kGaiaOAuth2LoginRefreshToken); 223 if (!auth_token_time.is_null()) { 224 base::TimeDelta age = base::Time::Now() - auth_token_time; 225 if (age < base::TimeDelta::FromHours(1)) { 226 UMA_HISTOGRAM_CUSTOM_TIMES( 227 "Sync.AuthInvalidationRejectedTokenAgeShort", 228 age, 229 base::TimeDelta::FromSeconds(1), 230 base::TimeDelta::FromHours(1), 231 50); 232 } 233 UMA_HISTOGRAM_COUNTS("Sync.AuthInvalidationRejectedTokenAgeLong", 234 age.InDays()); 235 } 236 invalidator_registrar_->UpdateInvalidatorState( 237 syncer::INVALIDATION_CREDENTIALS_REJECTED); 238 break; 239 } 240 default: { 241 // We have no way to notify the user of this. Do nothing. 242 } 243 } 244 } 245 246 void TiclInvalidationService::OnRefreshTokenAvailable( 247 const std::string& account_id) { 248 if (oauth2_token_service_->GetPrimaryAccountId() == account_id) { 249 if (!IsStarted() && IsReadyToStart()) { 250 StartInvalidator(); 251 } 252 } 253 } 254 255 void TiclInvalidationService::OnRefreshTokenRevoked( 256 const std::string& account_id) { 257 if (oauth2_token_service_->GetPrimaryAccountId() == account_id) { 258 access_token_.clear(); 259 if (IsStarted()) { 260 UpdateInvalidatorCredentials(); 261 } 262 } 263 } 264 265 void TiclInvalidationService::OnInvalidatorStateChange( 266 syncer::InvalidatorState state) { 267 if (state == syncer::INVALIDATION_CREDENTIALS_REJECTED) { 268 // This may be due to normal OAuth access token expiration. If so, we must 269 // fetch a new one using our refresh token. Resetting the invalidator's 270 // access token will not reset the invalidator's exponential backoff, so 271 // it's safe to try to update the token every time we receive this signal. 272 // 273 // We won't be receiving any invalidations while the refresh is in progress, 274 // we set our state to TRANSIENT_INVALIDATION_ERROR. If the credentials 275 // really are invalid, the refresh request should fail and 276 // OnGetTokenFailure() will put us into a INVALIDATION_CREDENTIALS_REJECTED 277 // state. 278 invalidator_registrar_->UpdateInvalidatorState( 279 syncer::TRANSIENT_INVALIDATION_ERROR); 280 RequestAccessToken(); 281 } else { 282 invalidator_registrar_->UpdateInvalidatorState(state); 283 } 284 } 285 286 void TiclInvalidationService::OnIncomingInvalidation( 287 const syncer::ObjectIdInvalidationMap& invalidation_map) { 288 invalidator_registrar_->DispatchInvalidationsToHandlers(invalidation_map); 289 } 290 291 void TiclInvalidationService::Shutdown() { 292 DCHECK(CalledOnValidThread()); 293 oauth2_token_service_->RemoveObserver(this); 294 if (IsStarted()) { 295 StopInvalidator(); 296 } 297 invalidator_storage_.reset(); 298 invalidator_registrar_.reset(); 299 } 300 301 bool TiclInvalidationService::IsReadyToStart() { 302 if (profile_->IsManaged()) { 303 DVLOG(2) << "Not starting TiclInvalidationService: User is managed."; 304 return false; 305 } 306 307 if (signin_manager_->GetAuthenticatedUsername().empty()) { 308 DVLOG(2) << "Not starting TiclInvalidationService: User is not signed in."; 309 return false; 310 } 311 312 if (!oauth2_token_service_) { 313 DVLOG(2) 314 << "Not starting TiclInvalidationService: " 315 << "OAuth2TokenService unavailable."; 316 return false; 317 } 318 319 if (!oauth2_token_service_->RefreshTokenIsAvailable( 320 oauth2_token_service_->GetPrimaryAccountId())) { 321 DVLOG(2) 322 << "Not starting TiclInvalidationServce: Waiting for refresh token."; 323 return false; 324 } 325 326 return true; 327 } 328 329 bool TiclInvalidationService::IsStarted() { 330 return invalidator_.get() != NULL; 331 } 332 333 void TiclInvalidationService::StartInvalidator() { 334 DCHECK(CalledOnValidThread()); 335 DCHECK(!invalidator_); 336 DCHECK(invalidator_storage_); 337 DCHECK(!invalidator_storage_->GetInvalidatorClientId().empty()); 338 339 if (access_token_.empty()) { 340 DVLOG(1) 341 << "TiclInvalidationService: " 342 << "Deferring start until we have an access token."; 343 RequestAccessToken(); 344 return; 345 } 346 347 notifier::NotifierOptions options = 348 ParseNotifierOptions(*CommandLine::ForCurrentProcess()); 349 options.request_context_getter = profile_->GetRequestContext(); 350 options.auth_mechanism = "X-OAUTH2"; 351 invalidator_.reset(new syncer::NonBlockingInvalidator( 352 options, 353 invalidator_storage_->GetInvalidatorClientId(), 354 invalidator_storage_->GetSavedInvalidations(), 355 invalidator_storage_->GetBootstrapData(), 356 syncer::WeakHandle<syncer::InvalidationStateTracker>( 357 invalidator_storage_->AsWeakPtr()), 358 content::GetUserAgent(GURL()))); 359 360 UpdateInvalidatorCredentials(); 361 362 invalidator_->RegisterHandler(this); 363 invalidator_->UpdateRegisteredIds( 364 this, 365 invalidator_registrar_->GetAllRegisteredIds()); 366 } 367 368 void TiclInvalidationService::UpdateInvalidatorCredentials() { 369 std::string email = signin_manager_->GetAuthenticatedUsername(); 370 371 DCHECK(!email.empty()) << "Expected user to be signed in."; 372 373 DVLOG(2) << "UpdateCredentials: " << email; 374 invalidator_->UpdateCredentials(email, access_token_); 375 } 376 377 void TiclInvalidationService::StopInvalidator() { 378 DCHECK(invalidator_); 379 invalidator_->UnregisterHandler(this); 380 invalidator_.reset(); 381 } 382 383 void TiclInvalidationService::Logout() { 384 access_token_request_.reset(); 385 request_access_token_retry_timer_.Stop(); 386 387 if (IsStarted()) { 388 StopInvalidator(); 389 } 390 391 // This service always expects to have a valid invalidator storage. 392 // So we must not only clear the old one, but also start a new one. 393 invalidator_storage_->Clear(); 394 invalidator_storage_.reset(new InvalidatorStorage(profile_->GetPrefs())); 395 invalidator_storage_->SetInvalidatorClientId(GenerateInvalidatorClientId()); 396 } 397 398 } // namespace invalidation 399