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