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.
5 #include "chrome/browser/invalidation/ticl_invalidation_service.h"
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"
23 static const char* kOAuth2Scopes
[] = {
24 GaiaConstants::kGoogleTalkOAuth2Scope
27 static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy
= {
28 // Number of initial errors (in sequence) to ignore before applying
29 // exponential back-off rules.
32 // Initial delay for exponential back-off in ms.
35 // Factor by which the waiting time will be multiplied.
38 // Fuzzing percentage. ex: 10% will spread requests randomly
39 // between 90%-100% of the calculated time.
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.
47 // Time to keep an entry from being discarded even when it
48 // has no significant state, -1 to never discard.
51 // Don't use initial delay unless the last request was an error.
55 namespace invalidation
{
57 TiclInvalidationService::TiclInvalidationService(
58 SigninManagerBase
* signin
,
59 ProfileOAuth2TokenService
* oauth2_token_service
,
61 : OAuth2TokenService::Consumer("ticl_invalidation"),
63 signin_manager_(signin
),
64 oauth2_token_service_(oauth2_token_service
),
65 invalidator_registrar_(new syncer::InvalidatorRegistrar()),
66 request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy
) {
69 TiclInvalidationService::~TiclInvalidationService() {
70 DCHECK(CalledOnValidThread());
73 void TiclInvalidationService::Init() {
74 DCHECK(CalledOnValidThread());
76 invalidator_storage_
.reset(new InvalidatorStorage(profile_
->GetPrefs()));
77 if (invalidator_storage_
->GetInvalidatorClientId().empty()) {
78 // This also clears any existing state. We can't reuse old invalidator
79 // state with the new ID anyway.
80 invalidator_storage_
->SetInvalidatorClientId(GenerateInvalidatorClientId());
83 if (IsReadyToStart()) {
84 StartInvalidator(PUSH_CLIENT_CHANNEL
);
87 notification_registrar_
.Add(this,
88 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
,
89 content::Source
<Profile
>(profile_
));
90 oauth2_token_service_
->AddObserver(this);
93 void TiclInvalidationService::InitForTest(syncer::Invalidator
* invalidator
) {
94 // Here we perform the equivalent of Init() and StartInvalidator(), but with
95 // some minor changes to account for the fact that we're injecting the
97 invalidator_
.reset(invalidator
);
99 invalidator_
->RegisterHandler(this);
100 invalidator_
->UpdateRegisteredIds(
102 invalidator_registrar_
->GetAllRegisteredIds());
105 void TiclInvalidationService::RegisterInvalidationHandler(
106 syncer::InvalidationHandler
* handler
) {
107 DCHECK(CalledOnValidThread());
108 DVLOG(2) << "Registering an invalidation handler";
109 invalidator_registrar_
->RegisterHandler(handler
);
112 void TiclInvalidationService::UpdateRegisteredInvalidationIds(
113 syncer::InvalidationHandler
* handler
,
114 const syncer::ObjectIdSet
& ids
) {
115 DCHECK(CalledOnValidThread());
116 DVLOG(2) << "Registering ids: " << ids
.size();
117 invalidator_registrar_
->UpdateRegisteredIds(handler
, ids
);
119 invalidator_
->UpdateRegisteredIds(
121 invalidator_registrar_
->GetAllRegisteredIds());
125 void TiclInvalidationService::UnregisterInvalidationHandler(
126 syncer::InvalidationHandler
* handler
) {
127 DCHECK(CalledOnValidThread());
128 DVLOG(2) << "Unregistering";
129 invalidator_registrar_
->UnregisterHandler(handler
);
131 invalidator_
->UpdateRegisteredIds(
133 invalidator_registrar_
->GetAllRegisteredIds());
137 syncer::InvalidatorState
TiclInvalidationService::GetInvalidatorState() const {
138 DCHECK(CalledOnValidThread());
140 DVLOG(2) << "GetInvalidatorState returning "
141 << invalidator_
->GetInvalidatorState();
142 return invalidator_
->GetInvalidatorState();
144 DVLOG(2) << "Invalidator currently stopped";
145 return syncer::TRANSIENT_INVALIDATION_ERROR
;
149 std::string
TiclInvalidationService::GetInvalidatorClientId() const {
150 DCHECK(CalledOnValidThread());
151 return invalidator_storage_
->GetInvalidatorClientId();
154 void TiclInvalidationService::Observe(
156 const content::NotificationSource
& source
,
157 const content::NotificationDetails
& details
) {
158 DCHECK(CalledOnValidThread());
159 DCHECK_EQ(type
, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
);
163 void TiclInvalidationService::RequestAccessToken() {
164 // Only one active request at a time.
165 if (access_token_request_
!= NULL
)
167 request_access_token_retry_timer_
.Stop();
168 OAuth2TokenService::ScopeSet oauth2_scopes
;
169 for (size_t i
= 0; i
< arraysize(kOAuth2Scopes
); i
++)
170 oauth2_scopes
.insert(kOAuth2Scopes
[i
]);
171 // Invalidate previous token, otherwise token service will return the same
173 const std::string
& account_id
= oauth2_token_service_
->GetPrimaryAccountId();
174 oauth2_token_service_
->InvalidateToken(account_id
,
177 access_token_
.clear();
178 access_token_request_
= oauth2_token_service_
->StartRequest(account_id
,
183 void TiclInvalidationService::OnGetTokenSuccess(
184 const OAuth2TokenService::Request
* request
,
185 const std::string
& access_token
,
186 const base::Time
& expiration_time
) {
187 DCHECK_EQ(access_token_request_
, request
);
188 access_token_request_
.reset();
189 // Reset backoff time after successful response.
190 request_access_token_backoff_
.Reset();
191 access_token_
= access_token
;
192 if (!IsStarted() && IsReadyToStart()) {
193 StartInvalidator(PUSH_CLIENT_CHANNEL
);
195 UpdateInvalidatorCredentials();
199 void TiclInvalidationService::OnGetTokenFailure(
200 const OAuth2TokenService::Request
* request
,
201 const GoogleServiceAuthError
& error
) {
202 DCHECK_EQ(access_token_request_
, request
);
203 DCHECK_NE(error
.state(), GoogleServiceAuthError::NONE
);
204 access_token_request_
.reset();
205 switch (error
.state()) {
206 case GoogleServiceAuthError::CONNECTION_FAILED
:
207 case GoogleServiceAuthError::SERVICE_UNAVAILABLE
: {
208 // Transient error. Retry after some time.
209 request_access_token_backoff_
.InformOfRequest(false);
210 request_access_token_retry_timer_
.Start(
212 request_access_token_backoff_
.GetTimeUntilRelease(),
213 base::Bind(&TiclInvalidationService::RequestAccessToken
,
214 base::Unretained(this)));
217 case GoogleServiceAuthError::SERVICE_ERROR
:
218 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
: {
219 invalidator_registrar_
->UpdateInvalidatorState(
220 syncer::INVALIDATION_CREDENTIALS_REJECTED
);
224 // We have no way to notify the user of this. Do nothing.
229 void TiclInvalidationService::OnRefreshTokenAvailable(
230 const std::string
& account_id
) {
231 if (oauth2_token_service_
->GetPrimaryAccountId() == account_id
) {
232 if (!IsStarted() && IsReadyToStart()) {
233 StartInvalidator(PUSH_CLIENT_CHANNEL
);
238 void TiclInvalidationService::OnRefreshTokenRevoked(
239 const std::string
& account_id
) {
240 if (oauth2_token_service_
->GetPrimaryAccountId() == account_id
) {
241 access_token_
.clear();
243 UpdateInvalidatorCredentials();
248 void TiclInvalidationService::OnInvalidatorStateChange(
249 syncer::InvalidatorState state
) {
250 if (state
== syncer::INVALIDATION_CREDENTIALS_REJECTED
) {
251 // This may be due to normal OAuth access token expiration. If so, we must
252 // fetch a new one using our refresh token. Resetting the invalidator's
253 // access token will not reset the invalidator's exponential backoff, so
254 // it's safe to try to update the token every time we receive this signal.
256 // We won't be receiving any invalidations while the refresh is in progress,
257 // we set our state to TRANSIENT_INVALIDATION_ERROR. If the credentials
258 // really are invalid, the refresh request should fail and
259 // OnGetTokenFailure() will put us into a INVALIDATION_CREDENTIALS_REJECTED
261 invalidator_registrar_
->UpdateInvalidatorState(
262 syncer::TRANSIENT_INVALIDATION_ERROR
);
263 RequestAccessToken();
265 invalidator_registrar_
->UpdateInvalidatorState(state
);
269 void TiclInvalidationService::OnIncomingInvalidation(
270 const syncer::ObjectIdInvalidationMap
& invalidation_map
) {
271 invalidator_registrar_
->DispatchInvalidationsToHandlers(invalidation_map
);
274 void TiclInvalidationService::Shutdown() {
275 DCHECK(CalledOnValidThread());
276 oauth2_token_service_
->RemoveObserver(this);
280 invalidator_storage_
.reset();
281 invalidator_registrar_
.reset();
284 bool TiclInvalidationService::IsReadyToStart() {
285 if (profile_
->IsManaged()) {
286 DVLOG(2) << "Not starting TiclInvalidationService: User is managed.";
290 if (signin_manager_
->GetAuthenticatedUsername().empty()) {
291 DVLOG(2) << "Not starting TiclInvalidationService: User is not signed in.";
295 if (!oauth2_token_service_
) {
297 << "Not starting TiclInvalidationService: "
298 << "OAuth2TokenService unavailable.";
302 if (!oauth2_token_service_
->RefreshTokenIsAvailable(
303 oauth2_token_service_
->GetPrimaryAccountId())) {
305 << "Not starting TiclInvalidationServce: Waiting for refresh token.";
312 bool TiclInvalidationService::IsStarted() {
313 return invalidator_
.get() != NULL
;
316 void TiclInvalidationService::StartInvalidator(
317 InvalidationNetworkChannel network_channel
) {
318 DCHECK(CalledOnValidThread());
319 DCHECK(!invalidator_
);
320 DCHECK(invalidator_storage_
);
321 DCHECK(!invalidator_storage_
->GetInvalidatorClientId().empty());
323 if (access_token_
.empty()) {
325 << "TiclInvalidationService: "
326 << "Deferring start until we have an access token.";
327 RequestAccessToken();
331 syncer::NetworkChannelCreator network_channel_creator
;
333 switch (network_channel
) {
334 case PUSH_CLIENT_CHANNEL
: {
335 notifier::NotifierOptions options
=
336 ParseNotifierOptions(*CommandLine::ForCurrentProcess());
337 options
.request_context_getter
= profile_
->GetRequestContext();
338 options
.auth_mechanism
= "X-OAUTH2";
339 DCHECK_EQ(notifier::NOTIFICATION_SERVER
, options
.notification_method
);
340 network_channel_creator
=
341 syncer::NonBlockingInvalidator::MakePushClientChannelCreator(options
);
344 case GCM_NETWORK_CHANNEL
: {
345 network_channel_creator
=
346 syncer::NonBlockingInvalidator::MakeGCMNetworkChannelCreator();
354 invalidator_
.reset(new syncer::NonBlockingInvalidator(
355 network_channel_creator
,
356 invalidator_storage_
->GetInvalidatorClientId(),
357 invalidator_storage_
->GetSavedInvalidations(),
358 invalidator_storage_
->GetBootstrapData(),
359 syncer::WeakHandle
<syncer::InvalidationStateTracker
>(
360 invalidator_storage_
->AsWeakPtr()),
361 content::GetUserAgent(GURL()),
362 profile_
->GetRequestContext()));
364 UpdateInvalidatorCredentials();
366 invalidator_
->RegisterHandler(this);
367 invalidator_
->UpdateRegisteredIds(
369 invalidator_registrar_
->GetAllRegisteredIds());
372 void TiclInvalidationService::UpdateInvalidatorCredentials() {
373 std::string email
= signin_manager_
->GetAuthenticatedUsername();
375 DCHECK(!email
.empty()) << "Expected user to be signed in.";
377 DVLOG(2) << "UpdateCredentials: " << email
;
378 invalidator_
->UpdateCredentials(email
, access_token_
);
381 void TiclInvalidationService::StopInvalidator() {
382 DCHECK(invalidator_
);
383 invalidator_
->UnregisterHandler(this);
384 invalidator_
.reset();
387 void TiclInvalidationService::Logout() {
388 access_token_request_
.reset();
389 request_access_token_retry_timer_
.Stop();
395 // This service always expects to have a valid invalidator storage.
396 // So we must not only clear the old one, but also start a new one.
397 invalidator_storage_
->Clear();
398 invalidator_storage_
.reset(new InvalidatorStorage(profile_
->GetPrefs()));
399 invalidator_storage_
->SetInvalidatorClientId(GenerateInvalidatorClientId());
402 } // namespace invalidation