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
TiclInvalidationService(SigninManagerBase * signin,ProfileOAuth2TokenService * oauth2_token_service,Profile * profile)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
~TiclInvalidationService()68 TiclInvalidationService::~TiclInvalidationService() {
69 DCHECK(CalledOnValidThread());
70 }
71
Init()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
InitForTest(syncer::Invalidator * invalidator)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
RegisterInvalidationHandler(syncer::InvalidationHandler * handler)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
UpdateRegisteredInvalidationIds(syncer::InvalidationHandler * handler,const syncer::ObjectIdSet & ids)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
UnregisterInvalidationHandler(syncer::InvalidationHandler * handler)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
GetInvalidatorState() const136 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
GetInvalidatorClientId() const148 std::string TiclInvalidationService::GetInvalidatorClientId() const {
149 DCHECK(CalledOnValidThread());
150 return invalidator_storage_->GetInvalidatorClientId();
151 }
152
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
RequestAccessToken()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
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)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
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)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
OnRefreshTokenAvailable(const std::string & account_id)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
OnRefreshTokenRevoked(const std::string & account_id)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
OnInvalidatorStateChange(syncer::InvalidatorState state)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
OnIncomingInvalidation(const syncer::ObjectIdInvalidationMap & invalidation_map)286 void TiclInvalidationService::OnIncomingInvalidation(
287 const syncer::ObjectIdInvalidationMap& invalidation_map) {
288 invalidator_registrar_->DispatchInvalidationsToHandlers(invalidation_map);
289 }
290
Shutdown()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
IsReadyToStart()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
IsStarted()329 bool TiclInvalidationService::IsStarted() {
330 return invalidator_.get() != NULL;
331 }
332
StartInvalidator()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
UpdateInvalidatorCredentials()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
StopInvalidator()377 void TiclInvalidationService::StopInvalidator() {
378 DCHECK(invalidator_);
379 invalidator_->UnregisterHandler(this);
380 invalidator_.reset();
381 }
382
Logout()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