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