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