OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chrome_to_mobile_service.h" | 5 #include "chrome/browser/chrome_to_mobile_service.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/guid.h" | 10 #include "base/guid.h" |
(...skipping 25 matching lines...) Expand all Loading... |
36 #include "content/public/browser/notification_details.h" | 36 #include "content/public/browser/notification_details.h" |
37 #include "content/public/browser/notification_source.h" | 37 #include "content/public/browser/notification_source.h" |
38 #include "content/public/browser/web_contents.h" | 38 #include "content/public/browser/web_contents.h" |
39 #include "google/cacheinvalidation/include/types.h" | 39 #include "google/cacheinvalidation/include/types.h" |
40 #include "google/cacheinvalidation/types.pb.h" | 40 #include "google/cacheinvalidation/types.pb.h" |
41 #include "google_apis/gaia/gaia_constants.h" | 41 #include "google_apis/gaia/gaia_constants.h" |
42 #include "google_apis/gaia/gaia_urls.h" | 42 #include "google_apis/gaia/gaia_urls.h" |
43 #include "google_apis/gaia/oauth2_access_token_fetcher.h" | 43 #include "google_apis/gaia/oauth2_access_token_fetcher.h" |
44 #include "net/base/escape.h" | 44 #include "net/base/escape.h" |
45 #include "net/base/load_flags.h" | 45 #include "net/base/load_flags.h" |
46 #include "net/http/http_status_code.h" | |
47 #include "net/url_request/url_fetcher.h" | 46 #include "net/url_request/url_fetcher.h" |
48 #include "net/url_request/url_request_context_getter.h" | 47 #include "net/url_request/url_request_context_getter.h" |
49 #include "sync/notifier/invalidation_util.h" | 48 #include "sync/notifier/invalidation_util.h" |
50 | 49 |
51 namespace { | 50 namespace { |
52 | 51 |
53 // The maximum number of retries for the URLFetcher requests. | 52 // The maximum number of retries for the URLFetcher requests. |
54 const size_t kMaxRetries = 5; | 53 const size_t kMaxRetries = 1; |
55 | 54 |
56 // The number of hours to delay before retrying certain failed operations. | 55 // The number of hours to delay before retrying authentication on failure. |
57 const size_t kDelayHours = 1; | 56 const size_t kAuthRetryDelayHours = 6; |
| 57 |
| 58 // The number of hours before subsequent search requests are allowed. |
| 59 // This value is used to throttle expensive cloud print search requests. |
| 60 // Note that this limitation does not hold across application restarts. |
| 61 const int kSearchRequestDelayHours = 24; |
58 | 62 |
59 // The sync invalidation object ID for Chrome to Mobile's mobile device list. | 63 // The sync invalidation object ID for Chrome to Mobile's mobile device list. |
60 // This corresponds with cloud print's server-side invalidation object ID. | 64 // This corresponds with cloud print's server-side invalidation object ID. |
61 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". | 65 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". |
62 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; | 66 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; |
63 | 67 |
64 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. | 68 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. |
65 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; | 69 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; |
66 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; | 70 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; |
67 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; | 71 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; |
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
311 } | 315 } |
312 | 316 |
313 void ChromeToMobileService::Observe( | 317 void ChromeToMobileService::Observe( |
314 int type, | 318 int type, |
315 const content::NotificationSource& source, | 319 const content::NotificationSource& source, |
316 const content::NotificationDetails& details) { | 320 const content::NotificationDetails& details) { |
317 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); | 321 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); |
318 TokenService::TokenAvailableDetails* token_details = | 322 TokenService::TokenAvailableDetails* token_details = |
319 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); | 323 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); |
320 // Invalidate the cloud print access token on Gaia login token updates. | 324 // Invalidate the cloud print access token on Gaia login token updates. |
321 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken || | 325 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) |
322 token_details->service() == GaiaConstants::kGaiaOAuth2LoginAccessToken) | |
323 access_token_.clear(); | 326 access_token_.clear(); |
324 } | 327 } |
325 | 328 |
326 void ChromeToMobileService::OnGetTokenSuccess( | 329 void ChromeToMobileService::OnGetTokenSuccess( |
327 const std::string& access_token, | 330 const std::string& access_token, |
328 const base::Time& expiration_time) { | 331 const base::Time& expiration_time) { |
329 DCHECK(!access_token.empty()); | 332 DCHECK(!access_token.empty()); |
330 access_token_fetcher_.reset(); | 333 access_token_fetcher_.reset(); |
331 auth_retry_timer_.Stop(); | 334 auth_retry_timer_.Stop(); |
332 access_token_ = access_token; | 335 access_token_ = access_token; |
333 | 336 |
334 while (!task_queue_.empty()) { | 337 while (!task_queue_.empty()) { |
335 // Post all tasks that were queued and waiting on a valid access token. | 338 // Post all tasks that were queued and waiting on a valid access token. |
336 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, | 339 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
337 task_queue_.front())) { | 340 task_queue_.front())) { |
338 NOTREACHED(); | 341 NOTREACHED(); |
339 } | 342 } |
340 task_queue_.pop(); | 343 task_queue_.pop(); |
341 } | 344 } |
342 } | 345 } |
343 | 346 |
344 void ChromeToMobileService::OnGetTokenFailure( | 347 void ChromeToMobileService::OnGetTokenFailure( |
345 const GoogleServiceAuthError& error) { | 348 const GoogleServiceAuthError& error) { |
346 LogMetric(BAD_TOKEN); | |
347 access_token_.clear(); | |
348 access_token_fetcher_.reset(); | 349 access_token_fetcher_.reset(); |
349 auth_retry_timer_.Stop(); | 350 auth_retry_timer_.Stop(); |
350 | 351 |
351 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), | 352 auth_retry_timer_.Start( |
352 auth_retry_timer_.GetCurrentDelay() * 2); | 353 FROM_HERE, base::TimeDelta::FromHours(kAuthRetryDelayHours), |
353 auth_retry_timer_.Start(FROM_HERE, delay, this, | 354 this, &ChromeToMobileService::RequestAccessToken); |
354 &ChromeToMobileService::RequestAccessToken); | |
355 | |
356 // Clear the mobile list, which may be (or become) out of date. | |
357 ListValue empty; | |
358 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, empty); | |
359 UpdateCommandState(); | |
360 } | 355 } |
361 | 356 |
362 void ChromeToMobileService::OnNotificationsEnabled() { | 357 void ChromeToMobileService::OnNotificationsEnabled() { |
363 sync_invalidation_enabled_ = true; | 358 sync_invalidation_enabled_ = true; |
364 UpdateCommandState(); | 359 UpdateCommandState(); |
365 } | 360 } |
366 | 361 |
367 void ChromeToMobileService::OnNotificationsDisabled( | 362 void ChromeToMobileService::OnNotificationsDisabled( |
368 syncer::NotificationsDisabledReason reason) { | 363 syncer::NotificationsDisabledReason reason) { |
369 sync_invalidation_enabled_ = false; | 364 sync_invalidation_enabled_ = false; |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
484 request->Start(); | 479 request->Start(); |
485 } | 480 } |
486 | 481 |
487 void ChromeToMobileService::RequestAccessToken() { | 482 void ChromeToMobileService::RequestAccessToken() { |
488 // Register to observe Gaia login refresh token updates. | 483 // Register to observe Gaia login refresh token updates. |
489 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); | 484 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
490 if (registrar_.IsEmpty()) | 485 if (registrar_.IsEmpty()) |
491 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, | 486 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, |
492 content::Source<TokenService>(token_service)); | 487 content::Source<TokenService>(token_service)); |
493 | 488 |
494 // Deny concurrent requests. | 489 // Deny concurrent requests and bail without a valid Gaia login refresh token. |
495 if (access_token_fetcher_.get()) | 490 if (access_token_fetcher_.get() || !token_service->HasOAuthLoginToken()) |
496 return; | 491 return; |
497 | 492 |
498 // Handle invalid login refresh tokens as a failure. | |
499 if (!token_service->HasOAuthLoginToken()) { | |
500 OnGetTokenFailure(GoogleServiceAuthError(GoogleServiceAuthError::NONE)); | |
501 return; | |
502 } | |
503 | |
504 auth_retry_timer_.Stop(); | 493 auth_retry_timer_.Stop(); |
505 access_token_fetcher_.reset( | 494 access_token_fetcher_.reset( |
506 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); | 495 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); |
507 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); | 496 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); |
508 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), | 497 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), |
509 gaia_urls->oauth2_chrome_client_secret(), | 498 gaia_urls->oauth2_chrome_client_secret(), |
510 token_service->GetOAuth2LoginRefreshToken(), | 499 token_service->GetOAuth2LoginRefreshToken(), |
511 std::vector<std::string>(1, kCloudPrintAuth)); | 500 std::vector<std::string>(1, kCloudPrintAuth)); |
512 } | 501 } |
513 | 502 |
514 void ChromeToMobileService::RequestDeviceSearch() { | 503 void ChromeToMobileService::RequestDeviceSearch() { |
515 search_retry_timer_.Stop(); | |
516 if (access_token_.empty()) { | 504 if (access_token_.empty()) { |
517 // Enqueue this task to perform after obtaining an access token. | 505 // Enqueue this task to perform after obtaining an access token. |
518 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, | 506 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, |
519 weak_ptr_factory_.GetWeakPtr())); | 507 weak_ptr_factory_.GetWeakPtr())); |
520 RequestAccessToken(); | 508 RequestAccessToken(); |
521 return; | 509 return; |
522 } | 510 } |
523 | 511 |
524 LogMetric(DEVICES_REQUESTED); | 512 LogMetric(DEVICES_REQUESTED); |
525 | 513 |
526 net::URLFetcher* search_request = net::URLFetcher::Create( | 514 net::URLFetcher* search_request = net::URLFetcher::Create( |
527 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); | 515 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); |
528 InitRequest(search_request); | 516 InitRequest(search_request); |
529 search_request->Start(); | 517 search_request->Start(); |
530 } | 518 } |
531 | 519 |
532 void ChromeToMobileService::HandleSearchResponse( | 520 void ChromeToMobileService::HandleSearchResponse( |
533 const net::URLFetcher* source) { | 521 const net::URLFetcher* source) { |
534 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 522 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
535 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); | 523 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); |
536 | 524 |
537 ListValue mobiles; | |
538 std::string data; | 525 std::string data; |
539 ListValue* list = NULL; | 526 ListValue* list = NULL; |
540 DictionaryValue* dictionary = NULL; | 527 DictionaryValue* dictionary = NULL; |
541 source->GetResponseAsString(&data); | 528 source->GetResponseAsString(&data); |
542 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 529 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
543 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && | 530 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && |
544 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { | 531 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { |
| 532 ListValue mobiles; |
545 std::string type, name, id; | 533 std::string type, name, id; |
546 DictionaryValue* printer = NULL; | 534 DictionaryValue* printer = NULL; |
547 DictionaryValue* mobile = NULL; | 535 DictionaryValue* mobile = NULL; |
548 for (size_t index = 0; index < list->GetSize(); ++index) { | 536 for (size_t index = 0; index < list->GetSize(); ++index) { |
549 if (list->GetDictionary(index, &printer) && | 537 if (list->GetDictionary(index, &printer) && |
550 printer->GetString("type", &type) && | 538 printer->GetString("type", &type) && |
551 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { | 539 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { |
552 // Copy just the requisite values from the full |printer| definition. | 540 // Copy just the requisite values from the full |printer| definition. |
553 if (printer->GetString("displayName", &name) && | 541 if (printer->GetString("displayName", &name) && |
554 printer->GetString("id", &id)) { | 542 printer->GetString("id", &id)) { |
555 mobile = new DictionaryValue(); | 543 mobile = new DictionaryValue(); |
556 mobile->SetString("type", type); | 544 mobile->SetString("type", type); |
557 mobile->SetString("name", name); | 545 mobile->SetString("name", name); |
558 mobile->SetString("id", id); | 546 mobile->SetString("id", id); |
559 mobiles.Append(mobile); | 547 mobiles.Append(mobile); |
560 } else { | 548 } else { |
561 NOTREACHED(); | 549 NOTREACHED(); |
562 } | 550 } |
563 } | 551 } |
564 } | 552 } |
565 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { | 553 |
566 LogMetric(BAD_SEARCH_AUTH); | 554 // Update the cached mobile device list in profile prefs. |
567 // Invalidate the access token and retry a delayed search on access errors. | 555 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); |
568 access_token_.clear(); | 556 |
569 search_retry_timer_.Stop(); | 557 if (HasMobiles()) |
570 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), | 558 LogMetric(DEVICES_AVAILABLE); |
571 search_retry_timer_.GetCurrentDelay() * 2); | 559 UpdateCommandState(); |
572 search_retry_timer_.Start(FROM_HERE, delay, this, | |
573 &ChromeToMobileService::RequestDeviceSearch); | |
574 } else { | |
575 LogMetric(BAD_SEARCH_OTHER); | |
576 } | 560 } |
577 | |
578 // Update the cached mobile device list in profile prefs. | |
579 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); | |
580 if (HasMobiles()) | |
581 LogMetric(DEVICES_AVAILABLE); | |
582 UpdateCommandState(); | |
583 } | 561 } |
584 | 562 |
585 void ChromeToMobileService::HandleSubmitResponse( | 563 void ChromeToMobileService::HandleSubmitResponse( |
586 const net::URLFetcher* source) { | 564 const net::URLFetcher* source) { |
587 // Get the success value from the cloud print server response data. | 565 // Get the success value from the cloud print server response data. |
588 std::string data; | 566 std::string data; |
| 567 source->GetResponseAsString(&data); |
589 bool success = false; | 568 bool success = false; |
590 source->GetResponseAsString(&data); | |
591 DictionaryValue* dictionary = NULL; | 569 DictionaryValue* dictionary = NULL; |
592 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 570 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
593 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) { | 571 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) |
594 dictionary->GetBoolean("success", &success); | 572 dictionary->GetBoolean("success", &success); |
595 int error_code = -1; | |
596 if (dictionary->GetInteger("errorCode", &error_code)) | |
597 LogMetric(error_code == 407 ? BAD_SEND_407 : BAD_SEND_ERROR); | |
598 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { | |
599 LogMetric(BAD_SEND_AUTH); | |
600 } else { | |
601 LogMetric(BAD_SEND_OTHER); | |
602 } | |
603 | 573 |
604 // Log each URL and [DELAYED_]SNAPSHOT job submission response. | 574 // Log each URL and [DELAYED_]SNAPSHOT job submission response. |
605 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); | 575 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); |
606 LOG_IF(INFO, !success) << "ChromeToMobile send failed (" << | |
607 source->GetResponseCode() << "): " << data; | |
608 | 576 |
609 // Get the observer for this job submission response. | 577 // Get the observer for this job submission response. |
610 base::WeakPtr<Observer> observer; | 578 base::WeakPtr<Observer> observer; |
611 RequestObserverMap::iterator i = request_observer_map_.find(source); | 579 RequestObserverMap::iterator i = request_observer_map_.find(source); |
612 if (i != request_observer_map_.end()) { | 580 if (i != request_observer_map_.end()) { |
613 observer = i->second; | 581 observer = i->second; |
614 request_observer_map_.erase(i); | 582 request_observer_map_.erase(i); |
615 } | 583 } |
616 | 584 |
617 // Check if the observer is waiting on a second response (url or snapshot). | 585 // Check if the observer is waiting on a second response (url or snapshot). |
618 for (RequestObserverMap::iterator other = request_observer_map_.begin(); | 586 for (RequestObserverMap::iterator other = request_observer_map_.begin(); |
619 observer.get() && (other != request_observer_map_.end()); ++other) { | 587 observer.get() && (other != request_observer_map_.end()); ++other) { |
620 if (other->second == observer) { | 588 if (other->second == observer) { |
621 // Delay reporting success until the second response is received. | 589 // Delay reporting success until the second response is received. |
622 if (success) | 590 if (success) |
623 return; | 591 return; |
624 | 592 |
625 // Report failure below and ignore the second response. | 593 // Report failure below and ignore the second response. |
626 request_observer_map_.erase(other); | 594 request_observer_map_.erase(other); |
627 break; | 595 break; |
628 } | 596 } |
629 } | 597 } |
630 | 598 |
631 if (observer.get()) | 599 if (observer.get()) |
632 observer->OnSendComplete(success); | 600 observer->OnSendComplete(success); |
633 } | 601 } |
OLD | NEW |