Index: chrome/browser/sync/profile_sync_service.cc |
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc |
index 54d8ad27fc0f5da78afea954b9d7475a3e859cae..bbc69cf44c34338feb2eb2fd713f4db38b426dd3 100644 |
--- a/chrome/browser/sync/profile_sync_service.cc |
+++ b/chrome/browser/sync/profile_sync_service.cc |
@@ -31,6 +31,8 @@ |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/browser/signin/about_signin_internals.h" |
#include "chrome/browser/signin/about_signin_internals_factory.h" |
+#include "chrome/browser/signin/profile_oauth2_token_service.h" |
+#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" |
#include "chrome/browser/signin/signin_manager.h" |
#include "chrome/browser/signin/signin_manager_factory.h" |
#include "chrome/browser/signin/token_service.h" |
@@ -112,23 +114,43 @@ const char* ProfileSyncService::kDevServerUrl = |
static const int kSyncClearDataTimeoutInSeconds = 60; // 1 minute. |
-static const char* kRelevantTokenServices[] = { |
- GaiaConstants::kSyncService |
+static const char* kOAuth2Scopes[] = { |
+ GaiaConstants::kChromeSyncOAuth2Scope, |
+ // GoogleTalk scope is needed for notifications. |
+ GaiaConstants::kGoogleTalkOAuth2Scope |
}; |
-static const int kRelevantTokenServicesCount = |
- arraysize(kRelevantTokenServices); |
+ |
static const char* kSyncUnrecoverableErrorHistogram = |
"Sync.UnrecoverableErrors"; |
-// Helper to check if the given token service is relevant for sync. |
-static bool IsTokenServiceRelevant(const std::string& service) { |
- for (int i = 0; i < kRelevantTokenServicesCount; ++i) { |
- if (service == kRelevantTokenServices[i]) |
- return true; |
- } |
- return false; |
-} |
+const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = { |
+ // Number of initial errors (in sequence) to ignore before applying |
+ // exponential back-off rules. |
+ 0, |
+ |
+ // Initial delay for exponential back-off in ms. |
+ 2000, |
+ |
+ // Factor by which the waiting time will be multiplied. |
+ 2, |
+ |
+ // Fuzzing percentage. ex: 10% will spread requests randomly |
+ // between 90%-100% of the calculated time. |
+ 0.2, // 20% |
+ |
+ // Maximum amount of time we are willing to delay our request in ms. |
+ // TODO(pavely): crbug.com/246686 ProfileSyncService should retry |
+ // RequestAccessToken on connection state change after backoff |
+ 1000 * 3600 * 4, // 4 hours. |
+ |
+ // Time to keep an entry from being discarded even when it |
+ // has no significant state, -1 to never discard. |
+ -1, |
+ |
+ // Don't use initial delay unless the last request was an error. |
+ false, |
+}; |
bool ShouldShowActionOnUI( |
const syncer::SyncProtocolError& error) { |
@@ -164,7 +186,8 @@ ProfileSyncService::ProfileSyncService(ProfileSyncComponentsFactory* factory, |
auto_start_enabled_(start_behavior == AUTO_START), |
configure_status_(DataTypeManager::UNKNOWN), |
setup_in_progress_(false), |
- invalidator_state_(syncer::DEFAULT_INVALIDATION_ERROR) { |
+ invalidator_state_(syncer::DEFAULT_INVALIDATION_ERROR), |
+ request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy) { |
// By default, dev, canary, and unbranded Chromium users will go to the |
// development servers. Development servers have more features than standard |
// sync servers. Users with officially-branded Chrome stable and beta builds |
@@ -196,11 +219,23 @@ bool ProfileSyncService::IsSyncEnabledAndLoggedIn() { |
return !GetEffectiveUsername().empty(); |
} |
-bool ProfileSyncService::IsSyncTokenAvailable() { |
- TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
- if (!token_service) |
- return false; |
- return token_service->HasTokenForService(GaiaConstants::kSyncService); |
+bool ProfileSyncService::IsOAuthRefreshTokenAvailable() { |
+ // Function name doesn't reflect which token is checked. Function checks |
+ // refresh token when use_oauth2_token_ is true (all platforms except android) |
+ // and sync token otherwise (for android). |
+ // TODO(pavely): Remove "else" part once use_oauth2_token_ is gone. |
+ if (use_oauth2_token_) { |
+ ProfileOAuth2TokenService* token_service = |
+ ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); |
+ if (!token_service) |
+ return false; |
+ return token_service->RefreshTokenIsAvailable(); |
+ } else { |
+ TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
+ if (!token_service) |
+ return false; |
+ return token_service->HasTokenForService(GaiaConstants::kSyncService); |
+ } |
} |
void ProfileSyncService::Initialize() { |
@@ -278,11 +313,20 @@ void ProfileSyncService::TryStart() { |
// (like ChromeOS) we don't start sync until tokens are loaded, because the |
// user can be "signed in" on those platforms long before the tokens get |
// loaded, and we don't want to generate spurious auth errors. |
- if (!IsSyncTokenAvailable() && |
+ if (!IsOAuthRefreshTokenAvailable() && |
!(!auto_start_enabled_ && token_service->TokensLoadedFromDB())) { |
return; |
} |
+ if (use_oauth2_token_) { |
+ // If we got here then tokens are loaded and user logged in and sync is |
+ // enabled. If OAuth refresh token is not available then something is wrong. |
+ // When PSS requests access token, OAuth2TokenService will return error and |
+ // PSS will show error to user asking to reauthenticate. |
+ UMA_HISTOGRAM_BOOLEAN("Sync.RefreshTokenAvailable", |
+ IsOAuthRefreshTokenAvailable()); |
+ } |
+ |
// If sync setup has completed we always start the backend. If the user is in |
// the process of setting up now, we should start the backend to download |
// account control state / encryption information). If autostart is enabled, |
@@ -406,26 +450,27 @@ void ProfileSyncService::InitSettings() { |
} |
} |
} |
+ |
+ use_oauth2_token_ = !command_line.HasSwitch( |
+ switches::kSyncDisableOAuth2Token); |
} |
SyncCredentials ProfileSyncService::GetCredentials() { |
SyncCredentials credentials; |
credentials.email = GetEffectiveUsername(); |
DCHECK(!credentials.email.empty()); |
- TokenService* service = TokenServiceFactory::GetForProfile(profile_); |
- if (service->HasTokenForService(GaiaConstants::kSyncService)) { |
- credentials.sync_token = service->GetTokenForService( |
- GaiaConstants::kSyncService); |
- credentials.sync_token_time = |
- AboutSigninInternalsFactory::GetForProfile(profile_)-> |
- GetTokenTime(GaiaConstants::kSyncService); |
- UMA_HISTOGRAM_BOOLEAN("Sync.CredentialsLost", false); |
+ if (use_oauth2_token_) { |
+ credentials.sync_token = access_token_; |
} else { |
- // We've lost our sync credentials (crbug.com/121755), so just make up some |
- // invalid credentials so the backend will generate an auth error. |
- UMA_HISTOGRAM_BOOLEAN("Sync.CredentialsLost", true); |
- credentials.sync_token = "credentials_lost"; |
+ TokenService* service = TokenServiceFactory::GetForProfile(profile_); |
+ if (service->HasTokenForService(GaiaConstants::kSyncService)) { |
+ credentials.sync_token = service->GetTokenForService( |
+ GaiaConstants::kSyncService); |
+ } |
} |
+ |
+ if (credentials.sync_token.empty()) |
+ credentials.sync_token = "credentials_lost"; |
return credentials; |
} |
@@ -492,6 +537,11 @@ void ProfileSyncService::StartUp(StartUpDeferredOption deferred_option) { |
DCHECK(IsSyncEnabledAndLoggedIn()); |
+ if (use_oauth2_token_ && access_token_.empty()) { |
+ RequestAccessToken(); |
+ return; |
+ } |
+ |
if (start_up_time_.is_null()) { |
start_up_time_ = base::Time::Now(); |
last_synced_time_ = sync_prefs_.GetLastSyncedTime(); |
@@ -639,6 +689,67 @@ syncer::InvalidatorState ProfileSyncService::GetInvalidatorState() const { |
return invalidator_registrar_->GetInvalidatorState(); |
} |
+void ProfileSyncService::OnGetTokenSuccess( |
+ const OAuth2TokenService::Request* request, |
+ const std::string& access_token, |
+ const base::Time& expiration_time) { |
+ DCHECK_EQ(access_token_request_, request); |
+ access_token_request_.reset(); |
+ // Reset backoff time after successful response. |
+ request_access_token_backoff_.Reset(); |
+ access_token_ = access_token; |
+ if (backend_) |
+ backend_->UpdateCredentials(GetCredentials()); |
+ else |
+ TryStart(); |
+} |
+ |
+void ProfileSyncService::OnGetTokenFailure( |
+ const OAuth2TokenService::Request* request, |
+ const GoogleServiceAuthError& error) { |
+ DCHECK_EQ(access_token_request_, request); |
+ DCHECK_NE(error.state(), GoogleServiceAuthError::NONE); |
+ access_token_request_.reset(); |
+ switch (error.state()) { |
+ case GoogleServiceAuthError::CONNECTION_FAILED: |
+ case GoogleServiceAuthError::SERVICE_UNAVAILABLE: { |
+ // Transient error. Retry after some time. |
+ request_access_token_backoff_.InformOfRequest(false); |
+ request_access_token_retry_timer_.Start( |
+ FROM_HERE, |
+ request_access_token_backoff_.GetTimeUntilRelease(), |
+ base::Bind(&ProfileSyncService::RequestAccessToken, |
+ weak_factory_.GetWeakPtr())); |
+ break; |
+ } |
+ case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: { |
+ // Report time since token was issued for invalid credentials error. |
+ // TODO(pavely): crbug.com/246817 Collect UMA histogram for auth token |
+ // rejections from invalidation service. |
+ base::Time auth_token_time = |
+ AboutSigninInternalsFactory::GetForProfile(profile_)-> |
+ GetTokenTime(GaiaConstants::kGaiaOAuth2LoginRefreshToken); |
+ if (!auth_token_time.is_null()) { |
+ base::TimeDelta age = base::Time::Now() - auth_token_time; |
+ if (age < base::TimeDelta::FromHours(1)) { |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Sync.AuthServerRejectedTokenAgeShort", |
+ age, |
+ base::TimeDelta::FromSeconds(1), |
+ base::TimeDelta::FromHours(1), |
+ 50); |
+ } |
+ UMA_HISTOGRAM_COUNTS("Sync.AuthServerRejectedTokenAgeLong", |
+ age.InDays()); |
+ } |
+ // Fallthrough. |
+ } |
+ default: { |
+ // Show error to user. |
+ UpdateAuthErrorState(error); |
+ } |
+ } |
+} |
+ |
void ProfileSyncService::EmitInvalidationForTest( |
const invalidation::ObjectId& id, |
const std::string& payload) { |
@@ -718,7 +829,7 @@ void ProfileSyncService::ShutdownImpl(bool sync_disabled) { |
encrypt_everything_ = false; |
encrypted_types_ = syncer::SyncEncryptionHandler::SensitiveTypes(); |
passphrase_required_reason_ = syncer::REASON_PASSPHRASE_NOT_REQUIRED; |
- start_up_time_ = base::Time(); |
+ request_access_token_retry_timer_.Stop(); |
// Revert to "no auth error". |
if (last_auth_error_.state() != GoogleServiceAuthError::NONE) |
UpdateAuthErrorState(GoogleServiceAuthError::AuthErrorNone()); |
@@ -1071,10 +1182,18 @@ AuthError ConnectionStatusToAuthError( |
void ProfileSyncService::OnConnectionStatusChange( |
syncer::ConnectionStatus status) { |
- const GoogleServiceAuthError auth_error = |
- ConnectionStatusToAuthError(status); |
- DVLOG(1) << "Connection status change: " << auth_error.ToString(); |
- UpdateAuthErrorState(auth_error); |
+ if (use_oauth2_token_ && status == syncer::CONNECTION_AUTH_ERROR) { |
+ // Sync or Tango server returned error indicating that access token is |
+ // invalid. It could be either expired or access is revoked. Let's request |
+ // another access token and if access is revoked then request for token will |
+ // fail with corresponding error. |
+ RequestAccessToken(); |
+ } else { |
+ const GoogleServiceAuthError auth_error = |
+ ConnectionStatusToAuthError(status); |
+ DVLOG(1) << "Connection status change: " << auth_error.ToString(); |
+ UpdateAuthErrorState(auth_error); |
+ } |
} |
void ProfileSyncService::OnStopSyncingPermanently() { |
@@ -1798,6 +1917,23 @@ void ProfileSyncService::ConsumeCachedPassphraseIfPossible() { |
SetEncryptionPassphrase(passphrase, IMPLICIT); |
} |
+void ProfileSyncService::RequestAccessToken() { |
+ // Only one active request at a time. |
+ if (access_token_request_ != NULL) |
+ return; |
+ request_access_token_retry_timer_.Stop(); |
+ OAuth2TokenService::ScopeSet oauth2_scopes; |
+ for (size_t i = 0; i < arraysize(kOAuth2Scopes); i++) |
+ oauth2_scopes.insert(kOAuth2Scopes[i]); |
+ OAuth2TokenService* token_service = |
+ ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); |
+ // Invalidate previous token, otherwise token service will return the same |
+ // token again. |
+ token_service->InvalidateToken(oauth2_scopes, access_token_); |
+ access_token_.clear(); |
+ access_token_request_ = token_service->StartRequest(oauth2_scopes, this); |
+} |
+ |
void ProfileSyncService::SetEncryptionPassphrase(const std::string& passphrase, |
PassphraseType type) { |
// This should only be called when the backend has been initialized. |
@@ -1877,6 +2013,8 @@ void ProfileSyncService::OnSyncManagedPrefChange(bool is_sync_managed) { |
void ProfileSyncService::Observe(int type, |
const content::NotificationSource& source, |
const content::NotificationDetails& details) { |
+ const char* sync_token_service = use_oauth2_token_ ? |
+ GaiaConstants::kGaiaOAuth2LoginRefreshToken : GaiaConstants::kSyncService; |
switch (type) { |
case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: { |
const GoogleServiceSigninSuccessDetails* successful = |
@@ -1901,27 +2039,31 @@ void ProfileSyncService::Observe(int type, |
break; |
} |
case chrome::NOTIFICATION_TOKEN_REQUEST_FAILED: { |
+ // TODO(atwilson): sync shouldn't report refresh token request failures. |
+ // TokenService should do that instead. |
const TokenService::TokenRequestFailedDetails& token_details = |
*(content::Details<const TokenService::TokenRequestFailedDetails>( |
details).ptr()); |
- if (IsTokenServiceRelevant(token_details.service()) && |
- !IsSyncTokenAvailable()) { |
- // The additional check around IsSyncTokenAvailable() above prevents us |
- // sounding the alarm if we actually have a valid token but a refresh |
- // attempt by TokenService failed for any variety of reasons (e.g. flaky |
- // network). It's possible the token we do have is also invalid, but in |
- // that case we should already have (or can expect) an auth error sent |
- // from the sync backend. |
+ if (token_details.service() == sync_token_service && |
+ !IsOAuthRefreshTokenAvailable()) { |
+ // The additional check around IsOAuthRefreshTokenAvailable() above |
+ // prevents us sounding the alarm if we actually have a valid token but |
+ // a refresh attempt by TokenService failed for any variety of reasons |
+ // (e.g. flaky network). It's possible the token we do have is also |
+ // invalid, but in that case we should already have (or can expect) an |
+ // auth error sent from the sync backend. |
AuthError error(AuthError::INVALID_GAIA_CREDENTIALS); |
UpdateAuthErrorState(error); |
} |
break; |
} |
case chrome::NOTIFICATION_TOKEN_AVAILABLE: { |
+ // TODO(atwilson): Listen for notifications on OAuth2TokenService |
+ // (crbug.com/243737) |
const TokenService::TokenAvailableDetails& token_details = |
*(content::Details<const TokenService::TokenAvailableDetails>( |
details).ptr()); |
- if (!IsTokenServiceRelevant(token_details.service())) |
+ if (token_details.service() != sync_token_service) |
break; |
} // Fall through. |
case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED: { |
@@ -1930,15 +2072,20 @@ void ProfileSyncService::Observe(int type, |
// Initialize the backend if sync is enabled. If the sync token was |
// not loaded, GetCredentials() will generate invalid credentials to |
// cause the backend to generate an auth error (crbug.com/121755). |
- if (backend_) |
- backend_->UpdateCredentials(GetCredentials()); |
- else |
+ if (backend_) { |
+ if (use_oauth2_token_) |
+ RequestAccessToken(); |
+ else |
+ backend_->UpdateCredentials(GetCredentials()); |
+ } else { |
TryStart(); |
+ } |
break; |
} |
case chrome::NOTIFICATION_TOKENS_CLEARED: { |
// GetCredentials() will generate invalid credentials to cause the backend |
// to generate an auth error. |
+ access_token_.clear(); |
if (backend_) |
backend_->UpdateCredentials(GetCredentials()); |
break; |
@@ -2075,4 +2222,3 @@ std::string ProfileSyncService::GetEffectiveUsername() { |
return signin_->GetAuthenticatedUsername(); |
} |
- |