| Index: chrome/browser/sync/credential_cache_service_win.cc
|
| diff --git a/chrome/browser/sync/credential_cache_service_win.cc b/chrome/browser/sync/credential_cache_service_win.cc
|
| index bd5e8de7aae84c37e04ab1b041cbf279614a0a5a..6dc7eeefb6be4c902cde7184b1862dadf9df295a 100644
|
| --- a/chrome/browser/sync/credential_cache_service_win.cc
|
| +++ b/chrome/browser/sync/credential_cache_service_win.cc
|
| @@ -9,6 +9,8 @@
|
| #include "base/base64.h"
|
| #include "base/compiler_specific.h"
|
| #include "base/file_util.h"
|
| +#include "base/string_number_conversions.h"
|
| +#include "base/time.h"
|
| #include "base/values.h"
|
| #include "base/win/windows_version.h"
|
| #include "chrome/browser/prefs/pref_service.h"
|
| @@ -33,6 +35,17 @@
|
|
|
| namespace syncer {
|
|
|
| +// The time delay (in seconds) between two consecutive polls of the alternate
|
| +// credential cache. A two minute delay seems like a reasonable amount of time
|
| +// in which to propagate changes to signed in state between Metro and Desktop.
|
| +const int kCredentialCachePollIntervalSecs = 2 * 60;
|
| +
|
| +// Keeps track of the last time a credential cache was written to. Used to make
|
| +// sure that we only apply changes from newer credential caches to older ones,
|
| +// and not vice versa.
|
| +const char kLastUpdatedTime[] = "last_updated_time";
|
| +
|
| +using base::TimeDelta;
|
| using content::BrowserThread;
|
|
|
| CredentialCacheService::CredentialCacheService(Profile* profile)
|
| @@ -64,9 +77,14 @@ void CredentialCacheService::Shutdown() {
|
|
|
| void CredentialCacheService::OnInitializationCompleted(bool succeeded) {
|
| DCHECK(succeeded);
|
| - // When the alternate credential store becomes available, begin consuming its
|
| - // cached credentials.
|
| - if (alternate_store_.get() && alternate_store_->IsInitializationComplete()) {
|
| + // When the local and alternate credential stores become available, begin
|
| + // consuming the alternate cached credentials. We must also wait for the local
|
| + // credential store because the credentials read from the alternate cache and
|
| + // applied locally must eventually get stored in the local cache.
|
| + if (alternate_store_.get() &&
|
| + alternate_store_->IsInitializationComplete() &&
|
| + local_store_.get() &&
|
| + local_store_->IsInitializationComplete()) {
|
| ReadCachedCredentialsFromAlternateProfile();
|
| }
|
| }
|
| @@ -176,6 +194,15 @@ std::string CredentialCacheService::UnpackCredential(
|
| return unencrypted;
|
| }
|
|
|
| +void CredentialCacheService::WriteLastUpdatedTime() {
|
| + DCHECK(local_store_.get());
|
| + int64 last_updated_time = base::TimeTicks::Now().ToInternalValue();
|
| + std::string last_updated_time_string = base::Int64ToString(last_updated_time);
|
| + local_store_->SetValueSilently(
|
| + kLastUpdatedTime,
|
| + base::Value::CreateStringValue(last_updated_time_string));
|
| +}
|
| +
|
| void CredentialCacheService::PackAndUpdateStringPref(
|
| const std::string& pref_name,
|
| const std::string& new_value) {
|
| @@ -187,6 +214,7 @@ void CredentialCacheService::PackAndUpdateStringPref(
|
| // sign-ins.
|
| local_store_->SetValueSilently(pref_name, PackCredential(std::string()));
|
| }
|
| + WriteLastUpdatedTime();
|
| }
|
|
|
| void CredentialCacheService::UpdateBooleanPref(const std::string& pref_name,
|
| @@ -194,13 +222,27 @@ void CredentialCacheService::UpdateBooleanPref(const std::string& pref_name,
|
| DCHECK(local_store_.get());
|
| if (!HasUserSignedOut()) {
|
| local_store_->SetValueSilently(pref_name,
|
| - base::Value::CreateBooleanValue(new_value));
|
| + base::Value::CreateBooleanValue(new_value));
|
| } else {
|
| // Write a default value of false since we cache credentials only for
|
| // first-time sign-ins.
|
| local_store_->SetValueSilently(pref_name,
|
| base::Value::CreateBooleanValue(false));
|
| }
|
| + WriteLastUpdatedTime();
|
| +}
|
| +
|
| +int64 CredentialCacheService::GetLastUpdatedTime(
|
| + scoped_refptr<JsonPrefStore> store) {
|
| + const base::Value* last_updated_time_value = NULL;
|
| + store->GetValue(kLastUpdatedTime, &last_updated_time_value);
|
| + std::string last_updated_time_string;
|
| + last_updated_time_value->GetAsString(&last_updated_time_string);
|
| + int64 last_updated_time;
|
| + bool success = base::StringToInt64(last_updated_time_string,
|
| + &last_updated_time);
|
| + DCHECK(success);
|
| + return last_updated_time;
|
| }
|
|
|
| std::string CredentialCacheService::GetAndUnpackStringPref(
|
| @@ -245,13 +287,10 @@ bool CredentialCacheService::ShouldLookForCachedCredentialsInAlternateProfile()
|
| // true:
|
| // 1) Sync is not disabled by policy.
|
| // 2) Sync startup is not suppressed.
|
| - // 3) No user is currently signed in to sync.
|
| - DCHECK(profile_);
|
| - PrefService* prefs = profile_->GetPrefs();
|
| - DCHECK(prefs);
|
| - return !sync_prefs_.IsManaged() &&
|
| - !sync_prefs_.IsStartSuppressed() &&
|
| - prefs->GetString(prefs::kGoogleServicesUsername).empty();
|
| + // Note that we do want to look for credentials in the alternate profile even
|
| + // if the local user is signed in, so we can detect a sign out originating
|
| + // from the alternate profile.
|
| + return !sync_prefs_.IsManaged() && !sync_prefs_.IsStartSuppressed();
|
| }
|
|
|
| void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
|
| @@ -259,6 +298,7 @@ void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
|
| GetCredentialPathInCurrentProfile(),
|
| content::BrowserThread::GetMessageLoopProxyForThread(
|
| content::BrowserThread::FILE));
|
| + local_store_->AddObserver(this);
|
| local_store_->ReadPrefsAsync(NULL);
|
|
|
| // Register for notifications for updates to the sync credentials, which are
|
| @@ -267,11 +307,12 @@ void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
|
| pref_registrar_.Add(prefs::kSyncEncryptionBootstrapToken, this);
|
| pref_registrar_.Add(prefs::kGoogleServicesUsername, this);
|
| pref_registrar_.Add(prefs::kSyncKeepEverythingSynced, this);
|
| - for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
|
| - if (i == NIGORI) // The NIGORI preference is not persisted.
|
| + ModelTypeSet all_types = syncer::ModelTypeSet::All();
|
| + for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
|
| + if (it.Get() == NIGORI) // The NIGORI preference is not persisted.
|
| continue;
|
| pref_registrar_.Add(
|
| - browser_sync::SyncPrefs::GetPrefNameForDataType(ModelTypeFromInt(i)),
|
| + browser_sync::SyncPrefs::GetPrefNameForDataType(it.Get()),
|
| this);
|
| }
|
|
|
| @@ -289,13 +330,16 @@ void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
|
| void CredentialCacheService::InitializeAlternateCredentialCacheReader(
|
| bool* should_initialize) {
|
| // If |should_initialize| is false, there was no credential cache in the
|
| - // alternate profile directory, and there is nothing to do.
|
| - // TODO(rsimha): Add a polling mechanism that periodically examines the
|
| - // credential file in the alternate profile directory so we can respond to the
|
| - // user signing in and signing out.
|
| + // alternate profile directory, and there is nothing to do right now. Schedule
|
| + // another read in the future and exit.
|
| DCHECK(should_initialize);
|
| - if (!*should_initialize)
|
| + if (!*should_initialize) {
|
| + ScheduleNextReadFromAlternateCredentialCache();
|
| return;
|
| + }
|
| +
|
| + // A credential cache file was found in the alternate profile. Prepare to
|
| + // consume its contents.
|
| alternate_store_ = new JsonPrefStore(
|
| GetCredentialPathInAlternateProfile(),
|
| content::BrowserThread::GetMessageLoopProxyForThread(
|
| @@ -316,18 +360,15 @@ bool CredentialCacheService::HasUserSignedOut() {
|
|
|
| namespace {
|
|
|
| -// Determines if credentials should be read from the alternate profile based
|
| -// on the existence of the local and alternate credential files. Returns
|
| -// true via |result| if there is a credential cache file in the alternate
|
| -// profile, but there isn't one in the local profile. Returns false otherwise.
|
| -void ShouldReadFromAlternateCache(
|
| - const FilePath& credential_path_in_current_profile,
|
| +// Determines if there is a sync credential cache in the alternate profile.
|
| +// Returns true via |result| if there is a credential cache file in the
|
| +// alternate profile. Returns false otherwise.
|
| +void AlternateCredentialCacheExists(
|
| const FilePath& credential_path_in_alternate_profile,
|
| bool* result) {
|
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
|
| DCHECK(result);
|
| - *result = !file_util::PathExists(credential_path_in_current_profile) &&
|
| - file_util::PathExists(credential_path_in_alternate_profile);
|
| + *result = file_util::PathExists(credential_path_in_alternate_profile);
|
| }
|
|
|
| } // namespace
|
| @@ -337,8 +378,7 @@ void CredentialCacheService::LookForCachedCredentialsInAlternateProfile() {
|
| content::BrowserThread::PostTaskAndReply(
|
| content::BrowserThread::FILE,
|
| FROM_HERE,
|
| - base::Bind(&ShouldReadFromAlternateCache,
|
| - GetCredentialPathInCurrentProfile(),
|
| + base::Bind(&AlternateCredentialCacheExists,
|
| GetCredentialPathInAlternateProfile(),
|
| should_initialize),
|
| base::Bind(
|
| @@ -348,16 +388,30 @@ void CredentialCacheService::LookForCachedCredentialsInAlternateProfile() {
|
| }
|
|
|
| void CredentialCacheService::ReadCachedCredentialsFromAlternateProfile() {
|
| + // If the local user has signed in and signed out, we do not consume cached
|
| + // credentials from the alternate profile. There is nothing more to do, now or
|
| + // later on.
|
| + if (HasUserSignedOut())
|
| + return;
|
| +
|
| + // Sanity check the alternate credential cache. If any string credentials
|
| + // are outright missing even though the file exists, something is awry with
|
| + // the alternate profile store. There is no sense in flagging an error as the
|
| + // problem lies in a different profile directory. There is nothing to do now.
|
| + // We schedule a future read from the alternate credential cache and return.
|
| DCHECK(alternate_store_.get());
|
| if (!HasPref(alternate_store_, prefs::kGoogleServicesUsername) ||
|
| !HasPref(alternate_store_, GaiaConstants::kGaiaLsid) ||
|
| !HasPref(alternate_store_, GaiaConstants::kGaiaSid) ||
|
| !HasPref(alternate_store_, prefs::kSyncEncryptionBootstrapToken) ||
|
| !HasPref(alternate_store_, prefs::kSyncKeepEverythingSynced)) {
|
| - VLOG(1) << "Could not find cached credentials.";
|
| + VLOG(1) << "Could not find cached credentials in \""
|
| + << GetCredentialPathInAlternateProfile().value() << "\".";
|
| + ScheduleNextReadFromAlternateCredentialCache();
|
| return;
|
| }
|
|
|
| + // Extract cached credentials from the alternate credential cache.
|
| std::string google_services_username =
|
| GetAndUnpackStringPref(alternate_store_, prefs::kGoogleServicesUsername);
|
| std::string lsid =
|
| @@ -367,45 +421,78 @@ void CredentialCacheService::ReadCachedCredentialsFromAlternateProfile() {
|
| std::string encryption_bootstrap_token =
|
| GetAndUnpackStringPref(alternate_store_,
|
| prefs::kSyncEncryptionBootstrapToken);
|
| - bool keep_everything_synced =
|
| - GetBooleanPref(alternate_store_, prefs::kSyncKeepEverythingSynced);
|
|
|
| - if (google_services_username.empty() ||
|
| - lsid.empty() ||
|
| - sid.empty() ||
|
| - encryption_bootstrap_token.empty()) {
|
| - VLOG(1) << "Found empty cached credentials.";
|
| + // Sign out of sync if the alternate profile has signed out the same user.
|
| + // There is no need to schedule any more reads of the alternate profile
|
| + // cache because we only apply cached credentials for first-time sign-ins.
|
| + if (ShouldSignOutOfSync(google_services_username)) {
|
| + VLOG(1) << "User has signed out on the other profile. Signing out.";
|
| + InitiateSignOut();
|
| return;
|
| }
|
|
|
| - bool datatype_prefs[MODEL_TYPE_COUNT] = { false };
|
| - for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
|
| - if (i == NIGORI) // The NIGORI preference is not persisted.
|
| - continue;
|
| + // Extract cached sync prefs from the alternate credential cache.
|
| + bool keep_everything_synced =
|
| + GetBooleanPref(alternate_store_, prefs::kSyncKeepEverythingSynced);
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + ModelTypeSet registered_types = service->GetRegisteredDataTypes();
|
| + ModelTypeSet preferred_types;
|
| + for (ModelTypeSet::Iterator it = registered_types.First();
|
| + it.Good();
|
| + it.Inc()) {
|
| std::string datatype_pref_name =
|
| - browser_sync::SyncPrefs::GetPrefNameForDataType(ModelTypeFromInt(i));
|
| + browser_sync::SyncPrefs::GetPrefNameForDataType(it.Get());
|
| if (!HasPref(alternate_store_, datatype_pref_name)) {
|
| - VLOG(1) << "Could not find cached datatype prefs.";
|
| - return;
|
| + // If there is no cached pref for a specific data type, it means that the
|
| + // user originally signed in with an older version of Chrome, and then
|
| + // upgraded to a version with a new datatype. In such cases, we leave the
|
| + // default initial datatype setting as false while reading cached
|
| + // credentials, just like we do in SyncPrefs::RegisterPreferences.
|
| + VLOG(1) << "Could not find cached datatype pref for "
|
| + << datatype_pref_name << " in "
|
| + << GetCredentialPathInAlternateProfile().value() << ".";
|
| + continue;
|
| + }
|
| + if (GetBooleanPref(alternate_store_, datatype_pref_name))
|
| + preferred_types.Put(it.Get());
|
| + }
|
| +
|
| + // Reconfigure if sync settings or credentials have changed in the alternate
|
| + // profile, but for the same user that is signed in to the local profile.
|
| + if (MayReconfigureSync(google_services_username)) {
|
| + if (HaveSyncPrefsChanged(keep_everything_synced, preferred_types)) {
|
| + VLOG(1) << "Sync prefs have changed in other profile. Reconfiguring.";
|
| + service->OnUserChoseDatatypes(keep_everything_synced, preferred_types);
|
| + }
|
| + if (HaveTokenServiceCredentialsChanged(lsid, sid)) {
|
| + VLOG(1) << "Token service credentials have changed in other profile.";
|
| + UpdateTokenServiceCredentials(lsid, sid);
|
| }
|
| - datatype_prefs[i] = GetBooleanPref(alternate_store_, datatype_pref_name);
|
| }
|
|
|
| - ApplyCachedCredentials(google_services_username,
|
| + // Sign in if we notice new cached credentials in the alternate profile.
|
| + if (ShouldSignInToSync(google_services_username,
|
| lsid,
|
| sid,
|
| - encryption_bootstrap_token,
|
| - keep_everything_synced,
|
| - datatype_prefs);
|
| + encryption_bootstrap_token)) {
|
| + InitiateSignInWithCachedCredentials(google_services_username,
|
| + encryption_bootstrap_token,
|
| + keep_everything_synced,
|
| + preferred_types);
|
| + UpdateTokenServiceCredentials(lsid, sid);
|
| + }
|
| +
|
| + // Schedule the next read from the alternate credential cache so that we can
|
| + // detect future reconfigures or sign outs.
|
| + ScheduleNextReadFromAlternateCredentialCache();
|
| }
|
|
|
| -void CredentialCacheService::ApplyCachedCredentials(
|
| +void CredentialCacheService::InitiateSignInWithCachedCredentials(
|
| const std::string& google_services_username,
|
| - const std::string& lsid,
|
| - const std::string& sid,
|
| const std::string& encryption_bootstrap_token,
|
| bool keep_everything_synced,
|
| - const bool datatype_prefs[]) {
|
| + ModelTypeSet preferred_types) {
|
| // Update the google username in the SigninManager and PrefStore.
|
| ProfileSyncService* service =
|
| ProfileSyncServiceFactory::GetForProfile(profile_);
|
| @@ -418,19 +505,13 @@ void CredentialCacheService::ApplyCachedCredentials(
|
| sync_prefs_.SetSyncSetupCompleted();
|
| sync_prefs_.SetEncryptionBootstrapToken(encryption_bootstrap_token);
|
| sync_prefs_.SetKeepEverythingSynced(keep_everything_synced);
|
| - syncer::ModelTypeSet registered_types;
|
| - syncer::ModelTypeSet preferred_types;
|
| - for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
|
| - if (i == NIGORI) // The NIGORI preference is not persisted.
|
| - continue;
|
| - registered_types.Put(ModelTypeFromInt(i));
|
| - if (datatype_prefs[i])
|
| - preferred_types.Put(ModelTypeFromInt(i));
|
| - }
|
| - sync_prefs_.SetPreferredDataTypes(registered_types, preferred_types);
|
| + sync_prefs_.SetPreferredDataTypes(service->GetRegisteredDataTypes(),
|
| + preferred_types);
|
| +}
|
|
|
| - // Update the lsid and sid in the TokenService and mint new tokens for all
|
| - // Chrome services.
|
| +void CredentialCacheService::UpdateTokenServiceCredentials(
|
| + const std::string& lsid,
|
| + const std::string& sid) {
|
| GaiaAuthConsumer::ClientLoginResult login_result;
|
| login_result.lsid = lsid;
|
| login_result.sid = sid;
|
| @@ -440,4 +521,107 @@ void CredentialCacheService::ApplyCachedCredentials(
|
| token_service->StartFetchingTokens();
|
| }
|
|
|
| +void CredentialCacheService::InitiateSignOut() {
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + service->DisableForUser();
|
| +}
|
| +
|
| +bool CredentialCacheService::HaveSyncPrefsChanged(
|
| + bool keep_everything_synced,
|
| + ModelTypeSet preferred_types) const {
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + ModelTypeSet local_preferred_types =
|
| + sync_prefs_.GetPreferredDataTypes(service->GetRegisteredDataTypes());
|
| + return
|
| + (keep_everything_synced != sync_prefs_.HasKeepEverythingSynced()) ||
|
| + !Difference(preferred_types, local_preferred_types).Empty();
|
| +}
|
| +
|
| +bool CredentialCacheService::HaveTokenServiceCredentialsChanged(
|
| + const std::string& lsid,
|
| + const std::string& sid) {
|
| + std::string local_lsid =
|
| + GetAndUnpackStringPref(local_store_, GaiaConstants::kGaiaLsid);
|
| + std::string local_sid =
|
| + GetAndUnpackStringPref(local_store_, GaiaConstants::kGaiaSid);
|
| + return local_lsid != lsid || local_sid != sid;
|
| +}
|
| +
|
| +bool CredentialCacheService::ShouldSignOutOfSync(
|
| + const std::string& google_services_username) {
|
| + // We must sign out of sync iff:
|
| + // 1) The user is signed in to the local profile.
|
| + // 2) The user has never signed out of the local profile in the past.
|
| + // 3) We noticed that the user has signed out of the alternate profile.
|
| + // 4) The user is not already in the process of configuring sync.
|
| + // 5) The alternate cache was updated more recently than the local cache.
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + return !service->signin()->GetAuthenticatedUsername().empty() &&
|
| + !HasUserSignedOut() &&
|
| + google_services_username.empty() &&
|
| + !service->setup_in_progress() &&
|
| + (GetLastUpdatedTime(alternate_store_) >
|
| + GetLastUpdatedTime(local_store_));
|
| +}
|
| +
|
| +bool CredentialCacheService::MayReconfigureSync(
|
| + const std::string& google_services_username) {
|
| + // We may attempt to reconfigure sync iff:
|
| + // 1) The user is signed in to the local profile.
|
| + // 2) The user has never signed out of the local profile in the past.
|
| + // 3) The user is signed in to the alternate profile with the same account.
|
| + // 4) The user is not already in the process of configuring sync.
|
| + // 5) The alternate cache was updated more recently than the local cache.
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + return !service->signin()->GetAuthenticatedUsername().empty() &&
|
| + !HasUserSignedOut() &&
|
| + (google_services_username ==
|
| + service->signin()->GetAuthenticatedUsername()) &&
|
| + !service->setup_in_progress() &&
|
| + (GetLastUpdatedTime(alternate_store_) >
|
| + GetLastUpdatedTime(local_store_));
|
| +}
|
| +
|
| +bool CredentialCacheService::ShouldSignInToSync(
|
| + const std::string& google_services_username,
|
| + const std::string& lsid,
|
| + const std::string& sid,
|
| + const std::string& encryption_bootstrap_token) {
|
| + // We should sign in with cached credentials from the alternate profile iff:
|
| + // 1) The user is not currently signed in to the local profile.
|
| + // 2) The user has never signed out of the local profile in the past.
|
| + // 3) Valid cached credentials are available in the alternate profile.
|
| + // 4) The user is not already in the process of configuring sync.
|
| + ProfileSyncService* service =
|
| + ProfileSyncServiceFactory::GetForProfile(profile_);
|
| + return service->signin()->GetAuthenticatedUsername().empty() &&
|
| + !HasUserSignedOut() &&
|
| + !google_services_username.empty() &&
|
| + !lsid.empty() &&
|
| + !sid.empty() &&
|
| + !encryption_bootstrap_token.empty() &&
|
| + !service->setup_in_progress();
|
| +}
|
| +
|
| +void CredentialCacheService::ScheduleNextReadFromAlternateCredentialCache() {
|
| + // We must reinitialize |alternate_store_| here because the underlying
|
| + // credential file in the alternate profile might have changed, and we must
|
| + // re-read it afresh.
|
| + if (alternate_store_.get()) {
|
| + alternate_store_->RemoveObserver(this);
|
| + alternate_store_.release();
|
| + }
|
| + next_read_.Reset(base::Bind(
|
| + &CredentialCacheService::LookForCachedCredentialsInAlternateProfile,
|
| + weak_factory_.GetWeakPtr()));
|
| + MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE,
|
| + next_read_.callback(),
|
| + TimeDelta::FromSeconds(kCredentialCachePollIntervalSecs));
|
| +}
|
| +
|
| } // namespace syncer
|
|
|