Home | History | Annotate | Download | only in invalidation
      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