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 "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