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
20static const char* kOAuth2Scopes[] = {
21  GaiaConstants::kGoogleTalkOAuth2Scope
22};
23
24static 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
52namespace invalidation {
53
54TiclInvalidationService::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
71TiclInvalidationService::~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
81void 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
101void 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
116void 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
124void 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
138void 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
151syncer::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
163std::string TiclInvalidationService::GetInvalidatorClientId() const {
164  DCHECK(CalledOnValidThread());
165  return invalidation_state_tracker_->GetInvalidatorClientId();
166}
167
168InvalidationLogger* TiclInvalidationService::GetInvalidationLogger() {
169  return &logger_;
170}
171
172IdentityProvider* TiclInvalidationService::GetIdentityProvider() {
173  return identity_provider_.get();
174}
175
176void 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
184void 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
202void 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
218void 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
248void TiclInvalidationService::OnRefreshTokenAvailable(
249    const std::string& account_id) {
250  if (!IsStarted() && IsReadyToStart())
251    StartInvalidator(network_channel_type_);
252}
253
254void TiclInvalidationService::OnRefreshTokenRevoked(
255    const std::string& account_id) {
256  access_token_.clear();
257  if (IsStarted())
258    UpdateInvalidatorCredentials();
259}
260
261void 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
276void TiclInvalidationService::OnUseGCMChannelChanged() {
277  UpdateInvalidationNetworkChannel();
278}
279
280void 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
302void TiclInvalidationService::OnIncomingInvalidation(
303    const syncer::ObjectIdInvalidationMap& invalidation_map) {
304  invalidator_registrar_->DispatchInvalidationsToHandlers(invalidation_map);
305
306  logger_.OnInvalidation(invalidation_map);
307}
308
309std::string TiclInvalidationService::GetOwnerName() const { return "TICL"; }
310
311bool 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
335bool TiclInvalidationService::IsStarted() const {
336  return invalidator_.get() != NULL;
337}
338
339void 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
407void 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
420void 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
429void TiclInvalidationService::StopInvalidator() {
430  DCHECK(invalidator_);
431  gcm_invalidation_bridge_.reset();
432  invalidator_->UnregisterHandler(this);
433  invalidator_.reset();
434}
435
436}  // namespace invalidation
437