Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2501)

Unified Diff: chrome/browser/sync/profile_sync_service.cc

Issue 15421011: Use OAuth2 token for sync (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/sync/profile_sync_service.h ('k') | chrome/browser/sync/profile_sync_service_android.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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();
}
-
« no previous file with comments | « chrome/browser/sync/profile_sync_service.h ('k') | chrome/browser/sync/profile_sync_service_android.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698