| OLD | NEW |
| (Empty) |
| 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/invalidation/gcm_invalidation_bridge.h" | |
| 10 #include "chrome/common/chrome_content_client.h" | |
| 11 #include "components/gcm_driver/gcm_driver.h" | |
| 12 #include "components/invalidation/invalidation_service_util.h" | |
| 13 #include "components/invalidation/non_blocking_invalidator.h" | |
| 14 #include "google_apis/gaia/gaia_constants.h" | |
| 15 #include "net/url_request/url_request_context_getter.h" | |
| 16 #include "sync/internal_api/public/base/invalidator_state.h" | |
| 17 #include "sync/notifier/invalidation_util.h" | |
| 18 #include "sync/notifier/invalidator.h" | |
| 19 #include "sync/notifier/object_id_invalidation_map.h" | |
| 20 | |
| 21 static const char* kOAuth2Scopes[] = { | |
| 22 GaiaConstants::kGoogleTalkOAuth2Scope | |
| 23 }; | |
| 24 | |
| 25 static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = { | |
| 26 // Number of initial errors (in sequence) to ignore before applying | |
| 27 // exponential back-off rules. | |
| 28 0, | |
| 29 | |
| 30 // Initial delay for exponential back-off in ms. | |
| 31 2000, | |
| 32 | |
| 33 // Factor by which the waiting time will be multiplied. | |
| 34 2, | |
| 35 | |
| 36 // Fuzzing percentage. ex: 10% will spread requests randomly | |
| 37 // between 90%-100% of the calculated time. | |
| 38 0.2, // 20% | |
| 39 | |
| 40 // Maximum amount of time we are willing to delay our request in ms. | |
| 41 // TODO(pavely): crbug.com/246686 ProfileSyncService should retry | |
| 42 // RequestAccessToken on connection state change after backoff | |
| 43 1000 * 3600 * 4, // 4 hours. | |
| 44 | |
| 45 // Time to keep an entry from being discarded even when it | |
| 46 // has no significant state, -1 to never discard. | |
| 47 -1, | |
| 48 | |
| 49 // Don't use initial delay unless the last request was an error. | |
| 50 false, | |
| 51 }; | |
| 52 | |
| 53 namespace invalidation { | |
| 54 | |
| 55 TiclInvalidationService::TiclInvalidationService( | |
| 56 scoped_ptr<IdentityProvider> identity_provider, | |
| 57 scoped_ptr<TiclSettingsProvider> settings_provider, | |
| 58 gcm::GCMDriver* gcm_driver, | |
| 59 const scoped_refptr<net::URLRequestContextGetter>& request_context) | |
| 60 : OAuth2TokenService::Consumer("ticl_invalidation"), | |
| 61 identity_provider_(identity_provider.Pass()), | |
| 62 settings_provider_(settings_provider.Pass()), | |
| 63 invalidator_registrar_(new syncer::InvalidatorRegistrar()), | |
| 64 request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy), | |
| 65 network_channel_type_(PUSH_CLIENT_CHANNEL), | |
| 66 gcm_driver_(gcm_driver), | |
| 67 request_context_(request_context), | |
| 68 logger_() {} | |
| 69 | |
| 70 TiclInvalidationService::~TiclInvalidationService() { | |
| 71 DCHECK(CalledOnValidThread()); | |
| 72 } | |
| 73 | |
| 74 void TiclInvalidationService::Init( | |
| 75 scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker) { | |
| 76 DCHECK(CalledOnValidThread()); | |
| 77 invalidation_state_tracker_ = invalidation_state_tracker.Pass(); | |
| 78 | |
| 79 if (invalidation_state_tracker_->GetInvalidatorClientId().empty()) { | |
| 80 invalidation_state_tracker_->ClearAndSetNewClientId( | |
| 81 GenerateInvalidatorClientId()); | |
| 82 } | |
| 83 | |
| 84 UpdateInvalidationNetworkChannel(); | |
| 85 if (IsReadyToStart()) { | |
| 86 StartInvalidator(network_channel_type_); | |
| 87 } | |
| 88 | |
| 89 identity_provider_->AddObserver(this); | |
| 90 identity_provider_->AddActiveAccountRefreshTokenObserver(this); | |
| 91 settings_provider_->AddObserver(this); | |
| 92 } | |
| 93 | |
| 94 void TiclInvalidationService::InitForTest( | |
| 95 scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker, | |
| 96 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 invalidation_state_tracker_ = invalidation_state_tracker.Pass(); | |
| 101 invalidator_.reset(invalidator); | |
| 102 | |
| 103 invalidator_->RegisterHandler(this); | |
| 104 invalidator_->UpdateRegisteredIds( | |
| 105 this, | |
| 106 invalidator_registrar_->GetAllRegisteredIds()); | |
| 107 } | |
| 108 | |
| 109 void TiclInvalidationService::RegisterInvalidationHandler( | |
| 110 syncer::InvalidationHandler* handler) { | |
| 111 DCHECK(CalledOnValidThread()); | |
| 112 DVLOG(2) << "Registering an invalidation handler"; | |
| 113 invalidator_registrar_->RegisterHandler(handler); | |
| 114 logger_.OnRegistration(handler->GetOwnerName()); | |
| 115 } | |
| 116 | |
| 117 void TiclInvalidationService::UpdateRegisteredInvalidationIds( | |
| 118 syncer::InvalidationHandler* handler, | |
| 119 const syncer::ObjectIdSet& ids) { | |
| 120 DCHECK(CalledOnValidThread()); | |
| 121 DVLOG(2) << "Registering ids: " << ids.size(); | |
| 122 invalidator_registrar_->UpdateRegisteredIds(handler, ids); | |
| 123 if (invalidator_) { | |
| 124 invalidator_->UpdateRegisteredIds( | |
| 125 this, | |
| 126 invalidator_registrar_->GetAllRegisteredIds()); | |
| 127 } | |
| 128 logger_.OnUpdateIds(invalidator_registrar_->GetSanitizedHandlersIdsMap()); | |
| 129 } | |
| 130 | |
| 131 void TiclInvalidationService::UnregisterInvalidationHandler( | |
| 132 syncer::InvalidationHandler* handler) { | |
| 133 DCHECK(CalledOnValidThread()); | |
| 134 DVLOG(2) << "Unregistering"; | |
| 135 invalidator_registrar_->UnregisterHandler(handler); | |
| 136 if (invalidator_) { | |
| 137 invalidator_->UpdateRegisteredIds( | |
| 138 this, | |
| 139 invalidator_registrar_->GetAllRegisteredIds()); | |
| 140 } | |
| 141 logger_.OnUnregistration(handler->GetOwnerName()); | |
| 142 } | |
| 143 | |
| 144 syncer::InvalidatorState TiclInvalidationService::GetInvalidatorState() const { | |
| 145 DCHECK(CalledOnValidThread()); | |
| 146 if (invalidator_) { | |
| 147 DVLOG(2) << "GetInvalidatorState returning " | |
| 148 << invalidator_->GetInvalidatorState(); | |
| 149 return invalidator_->GetInvalidatorState(); | |
| 150 } else { | |
| 151 DVLOG(2) << "Invalidator currently stopped"; | |
| 152 return syncer::TRANSIENT_INVALIDATION_ERROR; | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 std::string TiclInvalidationService::GetInvalidatorClientId() const { | |
| 157 DCHECK(CalledOnValidThread()); | |
| 158 return invalidation_state_tracker_->GetInvalidatorClientId(); | |
| 159 } | |
| 160 | |
| 161 InvalidationLogger* TiclInvalidationService::GetInvalidationLogger() { | |
| 162 return &logger_; | |
| 163 } | |
| 164 | |
| 165 IdentityProvider* TiclInvalidationService::GetIdentityProvider() { | |
| 166 return identity_provider_.get(); | |
| 167 } | |
| 168 | |
| 169 void TiclInvalidationService::RequestDetailedStatus( | |
| 170 base::Callback<void(const base::DictionaryValue&)> return_callback) const { | |
| 171 if (IsStarted()) { | |
| 172 return_callback.Run(network_channel_options_); | |
| 173 invalidator_->RequestDetailedStatus(return_callback); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 void TiclInvalidationService::RequestAccessToken() { | |
| 178 // Only one active request at a time. | |
| 179 if (access_token_request_ != NULL) | |
| 180 return; | |
| 181 request_access_token_retry_timer_.Stop(); | |
| 182 OAuth2TokenService::ScopeSet oauth2_scopes; | |
| 183 for (size_t i = 0; i < arraysize(kOAuth2Scopes); i++) | |
| 184 oauth2_scopes.insert(kOAuth2Scopes[i]); | |
| 185 // Invalidate previous token, otherwise token service will return the same | |
| 186 // token again. | |
| 187 const std::string& account_id = identity_provider_->GetActiveAccountId(); | |
| 188 OAuth2TokenService* token_service = identity_provider_->GetTokenService(); | |
| 189 token_service->InvalidateToken(account_id, oauth2_scopes, access_token_); | |
| 190 access_token_.clear(); | |
| 191 access_token_request_ = | |
| 192 token_service->StartRequest(account_id, oauth2_scopes, this); | |
| 193 } | |
| 194 | |
| 195 void TiclInvalidationService::OnGetTokenSuccess( | |
| 196 const OAuth2TokenService::Request* request, | |
| 197 const std::string& access_token, | |
| 198 const base::Time& expiration_time) { | |
| 199 DCHECK_EQ(access_token_request_, request); | |
| 200 access_token_request_.reset(); | |
| 201 // Reset backoff time after successful response. | |
| 202 request_access_token_backoff_.Reset(); | |
| 203 access_token_ = access_token; | |
| 204 if (!IsStarted() && IsReadyToStart()) { | |
| 205 StartInvalidator(network_channel_type_); | |
| 206 } else { | |
| 207 UpdateInvalidatorCredentials(); | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 void TiclInvalidationService::OnGetTokenFailure( | |
| 212 const OAuth2TokenService::Request* request, | |
| 213 const GoogleServiceAuthError& error) { | |
| 214 DCHECK_EQ(access_token_request_, request); | |
| 215 DCHECK_NE(error.state(), GoogleServiceAuthError::NONE); | |
| 216 access_token_request_.reset(); | |
| 217 switch (error.state()) { | |
| 218 case GoogleServiceAuthError::CONNECTION_FAILED: | |
| 219 case GoogleServiceAuthError::SERVICE_UNAVAILABLE: { | |
| 220 // Transient error. Retry after some time. | |
| 221 request_access_token_backoff_.InformOfRequest(false); | |
| 222 request_access_token_retry_timer_.Start( | |
| 223 FROM_HERE, | |
| 224 request_access_token_backoff_.GetTimeUntilRelease(), | |
| 225 base::Bind(&TiclInvalidationService::RequestAccessToken, | |
| 226 base::Unretained(this))); | |
| 227 break; | |
| 228 } | |
| 229 case GoogleServiceAuthError::SERVICE_ERROR: | |
| 230 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: { | |
| 231 invalidator_registrar_->UpdateInvalidatorState( | |
| 232 syncer::INVALIDATION_CREDENTIALS_REJECTED); | |
| 233 break; | |
| 234 } | |
| 235 default: { | |
| 236 // We have no way to notify the user of this. Do nothing. | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 void TiclInvalidationService::OnRefreshTokenAvailable( | |
| 242 const std::string& account_id) { | |
| 243 if (!IsStarted() && IsReadyToStart()) | |
| 244 StartInvalidator(network_channel_type_); | |
| 245 } | |
| 246 | |
| 247 void TiclInvalidationService::OnRefreshTokenRevoked( | |
| 248 const std::string& account_id) { | |
| 249 access_token_.clear(); | |
| 250 if (IsStarted()) | |
| 251 UpdateInvalidatorCredentials(); | |
| 252 } | |
| 253 | |
| 254 void TiclInvalidationService::OnActiveAccountLogout() { | |
| 255 access_token_request_.reset(); | |
| 256 request_access_token_retry_timer_.Stop(); | |
| 257 | |
| 258 if (IsStarted()) { | |
| 259 StopInvalidator(); | |
| 260 } | |
| 261 | |
| 262 // This service always expects to have a valid invalidation state. Thus, we | |
| 263 // must generate a new client ID to replace the existing one. Setting a new | |
| 264 // client ID also clears all other state. | |
| 265 invalidation_state_tracker_-> | |
| 266 ClearAndSetNewClientId(GenerateInvalidatorClientId()); | |
| 267 } | |
| 268 | |
| 269 void TiclInvalidationService::OnUseGCMChannelChanged() { | |
| 270 UpdateInvalidationNetworkChannel(); | |
| 271 } | |
| 272 | |
| 273 void TiclInvalidationService::OnInvalidatorStateChange( | |
| 274 syncer::InvalidatorState state) { | |
| 275 if (state == syncer::INVALIDATION_CREDENTIALS_REJECTED) { | |
| 276 // This may be due to normal OAuth access token expiration. If so, we must | |
| 277 // fetch a new one using our refresh token. Resetting the invalidator's | |
| 278 // access token will not reset the invalidator's exponential backoff, so | |
| 279 // it's safe to try to update the token every time we receive this signal. | |
| 280 // | |
| 281 // We won't be receiving any invalidations while the refresh is in progress, | |
| 282 // we set our state to TRANSIENT_INVALIDATION_ERROR. If the credentials | |
| 283 // really are invalid, the refresh request should fail and | |
| 284 // OnGetTokenFailure() will put us into a INVALIDATION_CREDENTIALS_REJECTED | |
| 285 // state. | |
| 286 invalidator_registrar_->UpdateInvalidatorState( | |
| 287 syncer::TRANSIENT_INVALIDATION_ERROR); | |
| 288 RequestAccessToken(); | |
| 289 } else { | |
| 290 invalidator_registrar_->UpdateInvalidatorState(state); | |
| 291 } | |
| 292 logger_.OnStateChange(state); | |
| 293 } | |
| 294 | |
| 295 void TiclInvalidationService::OnIncomingInvalidation( | |
| 296 const syncer::ObjectIdInvalidationMap& invalidation_map) { | |
| 297 invalidator_registrar_->DispatchInvalidationsToHandlers(invalidation_map); | |
| 298 | |
| 299 logger_.OnInvalidation(invalidation_map); | |
| 300 } | |
| 301 | |
| 302 std::string TiclInvalidationService::GetOwnerName() const { return "TICL"; } | |
| 303 | |
| 304 void TiclInvalidationService::Shutdown() { | |
| 305 DCHECK(CalledOnValidThread()); | |
| 306 settings_provider_->RemoveObserver(this); | |
| 307 identity_provider_->RemoveActiveAccountRefreshTokenObserver(this); | |
| 308 identity_provider_->RemoveObserver(this); | |
| 309 if (IsStarted()) { | |
| 310 StopInvalidator(); | |
| 311 } | |
| 312 invalidation_state_tracker_.reset(); | |
| 313 invalidator_registrar_.reset(); | |
| 314 } | |
| 315 | |
| 316 bool TiclInvalidationService::IsReadyToStart() { | |
| 317 if (identity_provider_->GetActiveAccountId().empty()) { | |
| 318 DVLOG(2) << "Not starting TiclInvalidationService: User is not signed in."; | |
| 319 return false; | |
| 320 } | |
| 321 | |
| 322 OAuth2TokenService* token_service = identity_provider_->GetTokenService(); | |
| 323 if (!token_service) { | |
| 324 DVLOG(2) | |
| 325 << "Not starting TiclInvalidationService: " | |
| 326 << "OAuth2TokenService unavailable."; | |
| 327 return false; | |
| 328 } | |
| 329 | |
| 330 if (!token_service->RefreshTokenIsAvailable( | |
| 331 identity_provider_->GetActiveAccountId())) { | |
| 332 DVLOG(2) | |
| 333 << "Not starting TiclInvalidationServce: Waiting for refresh token."; | |
| 334 return false; | |
| 335 } | |
| 336 | |
| 337 return true; | |
| 338 } | |
| 339 | |
| 340 bool TiclInvalidationService::IsStarted() const { | |
| 341 return invalidator_.get() != NULL; | |
| 342 } | |
| 343 | |
| 344 void TiclInvalidationService::StartInvalidator( | |
| 345 InvalidationNetworkChannel network_channel) { | |
| 346 DCHECK(CalledOnValidThread()); | |
| 347 DCHECK(!invalidator_); | |
| 348 DCHECK(invalidation_state_tracker_); | |
| 349 DCHECK(!invalidation_state_tracker_->GetInvalidatorClientId().empty()); | |
| 350 | |
| 351 // Request access token for PushClientChannel. GCMNetworkChannel will request | |
| 352 // access token before sending message to server. | |
| 353 if (network_channel == PUSH_CLIENT_CHANNEL && access_token_.empty()) { | |
| 354 DVLOG(1) | |
| 355 << "TiclInvalidationService: " | |
| 356 << "Deferring start until we have an access token."; | |
| 357 RequestAccessToken(); | |
| 358 return; | |
| 359 } | |
| 360 | |
| 361 syncer::NetworkChannelCreator network_channel_creator; | |
| 362 | |
| 363 switch (network_channel) { | |
| 364 case PUSH_CLIENT_CHANNEL: { | |
| 365 notifier::NotifierOptions options = | |
| 366 ParseNotifierOptions(*CommandLine::ForCurrentProcess()); | |
| 367 options.request_context_getter = request_context_; | |
| 368 options.auth_mechanism = "X-OAUTH2"; | |
| 369 network_channel_options_.SetString("Options.HostPort", | |
| 370 options.xmpp_host_port.ToString()); | |
| 371 network_channel_options_.SetString("Options.AuthMechanism", | |
| 372 options.auth_mechanism); | |
| 373 DCHECK_EQ(notifier::NOTIFICATION_SERVER, options.notification_method); | |
| 374 network_channel_creator = | |
| 375 syncer::NonBlockingInvalidator::MakePushClientChannelCreator(options); | |
| 376 break; | |
| 377 } | |
| 378 case GCM_NETWORK_CHANNEL: { | |
| 379 gcm_invalidation_bridge_.reset(new GCMInvalidationBridge( | |
| 380 gcm_driver_, identity_provider_.get())); | |
| 381 network_channel_creator = | |
| 382 syncer::NonBlockingInvalidator::MakeGCMNetworkChannelCreator( | |
| 383 request_context_, | |
| 384 gcm_invalidation_bridge_->CreateDelegate().Pass()); | |
| 385 break; | |
| 386 } | |
| 387 default: { | |
| 388 NOTREACHED(); | |
| 389 return; | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 UMA_HISTOGRAM_ENUMERATION( | |
| 394 "Invalidations.NetworkChannel", network_channel, NETWORK_CHANNELS_COUNT); | |
| 395 invalidator_.reset(new syncer::NonBlockingInvalidator( | |
| 396 network_channel_creator, | |
| 397 invalidation_state_tracker_->GetInvalidatorClientId(), | |
| 398 invalidation_state_tracker_->GetSavedInvalidations(), | |
| 399 invalidation_state_tracker_->GetBootstrapData(), | |
| 400 invalidation_state_tracker_.get(), | |
| 401 GetUserAgent(), | |
| 402 request_context_)); | |
| 403 | |
| 404 UpdateInvalidatorCredentials(); | |
| 405 | |
| 406 invalidator_->RegisterHandler(this); | |
| 407 invalidator_->UpdateRegisteredIds( | |
| 408 this, | |
| 409 invalidator_registrar_->GetAllRegisteredIds()); | |
| 410 } | |
| 411 | |
| 412 void TiclInvalidationService::UpdateInvalidationNetworkChannel() { | |
| 413 const InvalidationNetworkChannel network_channel_type = | |
| 414 settings_provider_->UseGCMChannel() ? GCM_NETWORK_CHANNEL | |
| 415 : PUSH_CLIENT_CHANNEL; | |
| 416 if (network_channel_type_ == network_channel_type) | |
| 417 return; | |
| 418 network_channel_type_ = network_channel_type; | |
| 419 if (IsStarted()) { | |
| 420 StopInvalidator(); | |
| 421 StartInvalidator(network_channel_type_); | |
| 422 } | |
| 423 } | |
| 424 | |
| 425 void TiclInvalidationService::UpdateInvalidatorCredentials() { | |
| 426 std::string email = identity_provider_->GetActiveAccountId(); | |
| 427 | |
| 428 DCHECK(!email.empty()) << "Expected user to be signed in."; | |
| 429 | |
| 430 DVLOG(2) << "UpdateCredentials: " << email; | |
| 431 invalidator_->UpdateCredentials(email, access_token_); | |
| 432 } | |
| 433 | |
| 434 void TiclInvalidationService::StopInvalidator() { | |
| 435 DCHECK(invalidator_); | |
| 436 gcm_invalidation_bridge_.reset(); | |
| 437 invalidator_->UnregisterHandler(this); | |
| 438 invalidator_.reset(); | |
| 439 } | |
| 440 | |
| 441 } // namespace invalidation | |
| OLD | NEW |