// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync/credential_cache_service_win.h"

#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.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"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/signin/token_service.h"
#include "chrome/browser/signin/token_service_factory.h"
#include "chrome/browser/sync/credential_cache_service_factory_win.h"
#include "chrome/browser/sync/glue/chrome_encryptor.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_constants.h"
#include "sync/internal_api/public/base/model_type.h"

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 kLastCacheUpdateTimeKey[] = "last_cache_update_time";

// Deprecated. We were previously using base::TimeTicks as a timestamp. This was
// bad because TimeTicks values roll over on machine restart. We now use
// base::Time as a timestamp. However, we can't simply reuse "last_updated_time"
// because it may result in a Time value being compared with a TimeTicks value.
const char kLastUpdatedTimeTicksDeprecated[] = "last_updated_time";

using base::TimeDelta;
using content::BrowserThread;

CredentialCacheService::CredentialCacheService(Profile* profile)
    : profile_(profile),
      // |profile_| is null in unit tests.
      sync_prefs_(profile_ ? profile_->GetPrefs() : NULL),
      weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
  if (profile_) {
    InitializeLocalCredentialCacheWriter();
    // If sync is not disabled, look for credentials in the alternate profile.
    // Note that we do want to look for credentials in the alternate profile
    // even if the local user is signed in, so that we can detect a sign out or
    // reconfigure originating from the alternate profile.
    if (!sync_prefs_.IsManaged())
      LookForCachedCredentialsInAlternateProfile();
  }
}

CredentialCacheService::~CredentialCacheService() {
  Shutdown();
}

void CredentialCacheService::Shutdown() {
  if (local_store_.get())
    local_store_->CommitPendingWrite();
  local_store_observer_.release();
  local_store_.release();
  alternate_store_observer_.release();
  alternate_store_.release();
}

void CredentialCacheService::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(local_store_.get());
  switch (type) {
    case chrome::NOTIFICATION_PREF_CHANGED: {
      // One of the two sync encryption tokens has changed. Update its value in
      // the local cache.
      const std::string pref_name =
          *(content::Details<const std::string>(details).ptr());
      if (pref_name == prefs::kSyncEncryptionBootstrapToken) {
        PackAndUpdateStringPref(pref_name,
                                sync_prefs_.GetEncryptionBootstrapToken());
      } else if (pref_name == prefs::kSyncKeystoreEncryptionBootstrapToken) {
        PackAndUpdateStringPref(
            pref_name,
            sync_prefs_.GetKeystoreEncryptionBootstrapToken());
      } else {
        NOTREACHED() "Invalid pref name " << pref_name << ".";
      }
      break;
    }

    case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: {
      // The user has signed out. Write blank values to the google username,
      // encryption tokens and token service credentials in the local cache.
      PackAndUpdateStringPref(prefs::kGoogleServicesUsername, std::string());
      if (HasPref(local_store_, prefs::kSyncEncryptionBootstrapToken)) {
        PackAndUpdateStringPref(prefs::kSyncEncryptionBootstrapToken,
                                std::string());
      }
      if (HasPref(local_store_, prefs::kSyncKeystoreEncryptionBootstrapToken)) {
        PackAndUpdateStringPref(prefs::kSyncKeystoreEncryptionBootstrapToken,
                                std::string());
      }
      PackAndUpdateStringPref(GaiaConstants::kGaiaLsid, std::string());
      PackAndUpdateStringPref(GaiaConstants::kGaiaSid, std::string());
      break;
    }

    case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: {
      // The user has signed in. Write the new value of the google username to
      // the local cache.
      SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
      PackAndUpdateStringPref(prefs::kGoogleServicesUsername,
                              signin->GetAuthenticatedUsername());
      break;
    }

    case chrome::NOTIFICATION_SYNC_CONFIGURE_DONE: {
      // Local sync configuration is done. The sync service is now ready to
      // be reconfigured. Immediately look for any unconsumed config changes in
      // the alternate profile. If all changes have been consumed, this is a
      // no-op.
      ScheduleNextReadFromAlternateCredentialCache(0);
      break;
    }

    case chrome::NOTIFICATION_SYNC_CONFIGURE_START: {
      // We have detected a sync sign in, auto-start or reconfigure. Write the
      // latest sync preferences to the local cache.
      WriteSyncPrefsToLocalCache();
      break;
    }

    case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED: {
      // The token service has been fully initialized. Update the token service
      // credentials in the local cache. This is a no-op if the cache already
      // contains the latest values.
      TokenService* token_service =
          TokenServiceFactory::GetForProfile(profile_);
      if (token_service->AreCredentialsValid()) {
        GaiaAuthConsumer::ClientLoginResult credentials =
            token_service->credentials();
        PackAndUpdateStringPref(GaiaConstants::kGaiaLsid, credentials.lsid);
        PackAndUpdateStringPref(GaiaConstants::kGaiaSid, credentials.sid);
      }
      break;
    }

    case chrome::NOTIFICATION_TOKEN_SERVICE_CREDENTIALS_UPDATED: {
      // The token service has new credentials. Write them to the local cache.
      const TokenService::CredentialsUpdatedDetails& token_details =
          *(content::Details<const TokenService::CredentialsUpdatedDetails>(
              details).ptr());
      PackAndUpdateStringPref(GaiaConstants::kGaiaLsid, token_details.lsid());
      PackAndUpdateStringPref(GaiaConstants::kGaiaSid, token_details.sid());
      break;
    }

    case chrome::NOTIFICATION_TOKENS_CLEARED: {
      // Tokens have been cleared. Blank out lsid and sid in the local cache.
      PackAndUpdateStringPref(GaiaConstants::kGaiaLsid, std::string());
      PackAndUpdateStringPref(GaiaConstants::kGaiaSid, std::string());
      break;
    }

    default: {
      NOTREACHED();
      break;
    }
  }
}

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. Note that it is sufficient to
  // have just one of the two sync encryption tokens. 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::kSyncKeystoreEncryptionBootstrapToken)) ||
      !HasPref(alternate_store_, prefs::kSyncKeepEverythingSynced)) {
    VLOG(1) << "Could not find cached credentials in \""
            << GetCredentialPathInAlternateProfile().value() << "\".";
    ScheduleNextReadFromAlternateCredentialCache(
        kCredentialCachePollIntervalSecs);
    return;
  }

  // Extract the google username, lsid and sid from the alternate credential
  // cache.
  std::string alternate_google_services_username =
      GetAndUnpackStringPref(alternate_store_, prefs::kGoogleServicesUsername);
  std::string alternate_lsid =
      GetAndUnpackStringPref(alternate_store_, GaiaConstants::kGaiaLsid);
  std::string alternate_sid =
      GetAndUnpackStringPref(alternate_store_, GaiaConstants::kGaiaSid);

  // Extract the sync encryption tokens from the alternate credential cache.
  // Both tokens may not be found, since only one of them is used at any time.
  std::string alternate_encryption_bootstrap_token;
  if (HasPref(alternate_store_, prefs::kSyncEncryptionBootstrapToken)) {
    alternate_encryption_bootstrap_token =
        GetAndUnpackStringPref(alternate_store_,
                               prefs::kSyncEncryptionBootstrapToken);
  }
  std::string alternate_keystore_encryption_bootstrap_token;
  if (HasPref(alternate_store_, prefs::kSyncKeystoreEncryptionBootstrapToken)) {
    alternate_keystore_encryption_bootstrap_token =
        GetAndUnpackStringPref(alternate_store_,
                               prefs::kSyncKeystoreEncryptionBootstrapToken);
  }

  // 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(alternate_google_services_username)) {
    VLOG(1) << "User has signed out on the other profile. Signing out.";
    InitiateSignOut();
    return;
  }

  // Extract cached sync prefs from the alternate credential cache.
  bool alternate_keep_everything_synced =
      GetBooleanPref(alternate_store_, prefs::kSyncKeepEverythingSynced);
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  ModelTypeSet registered_types = service->GetRegisteredDataTypes();
  ModelTypeSet alternate_preferred_types;
  for (ModelTypeSet::Iterator it = registered_types.First();
       it.Good();
       it.Inc()) {
    std::string datatype_pref_name =
        browser_sync::SyncPrefs::GetPrefNameForDataType(it.Get());
    if (!HasPref(alternate_store_, datatype_pref_name)) {
      // 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))
      alternate_preferred_types.Put(it.Get());
  }

  // Reconfigure if sync settings, encryption tokens or token service
  // credentials have changed in the alternate profile, but for the same user
  // that is signed in to the local profile.
  if (MayReconfigureSync(alternate_google_services_username)) {
    if (HaveSyncPrefsChanged(alternate_keep_everything_synced,
                             alternate_preferred_types)) {
      VLOG(1) << "Sync prefs have changed in other profile. Reconfiguring.";
      service->OnUserChoseDatatypes(alternate_keep_everything_synced,
                                    alternate_preferred_types);
    }
    if (HaveSyncEncryptionTokensChanged(
        alternate_encryption_bootstrap_token,
        alternate_keystore_encryption_bootstrap_token)) {
      VLOG(1) << "Sync encryption tokens have changed in other profile.";
      sync_prefs_.SetEncryptionBootstrapToken(
          alternate_encryption_bootstrap_token);
      sync_prefs_.SetKeystoreEncryptionBootstrapToken(
          alternate_keystore_encryption_bootstrap_token);
    }
    if (HaveTokenServiceCredentialsChanged(alternate_lsid, alternate_sid)) {
      VLOG(1) << "Token service credentials have changed in other profile.";
      UpdateTokenServiceCredentials(alternate_lsid, alternate_sid);
    }
  }

  // Sign in if we notice new cached credentials in the alternate profile.
  if (ShouldSignInToSync(alternate_google_services_username,
                         alternate_lsid,
                         alternate_sid,
                         alternate_encryption_bootstrap_token,
                         alternate_keystore_encryption_bootstrap_token)) {
    InitiateSignInWithCachedCredentials(
        alternate_google_services_username,
        alternate_encryption_bootstrap_token,
        alternate_keystore_encryption_bootstrap_token,
        alternate_keep_everything_synced,
        alternate_preferred_types);
    UpdateTokenServiceCredentials(alternate_lsid, alternate_sid);
  }

  // Schedule the next read from the alternate credential cache so that we can
  // detect future reconfigures or sign outs.
  ScheduleNextReadFromAlternateCredentialCache(
      kCredentialCachePollIntervalSecs);
}

void CredentialCacheService::WriteSyncPrefsToLocalCache() {
  UpdateBooleanPref(prefs::kSyncKeepEverythingSynced,
                    sync_prefs_.HasKeepEverythingSynced());
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  ModelTypeSet registered_types = service->GetRegisteredDataTypes();
  for (ModelTypeSet::Iterator it = registered_types.First();
       it.Good();
       it.Inc()) {
    std::string datatype_pref_name =
        browser_sync::SyncPrefs::GetPrefNameForDataType(it.Get());
    UpdateBooleanPref(
        datatype_pref_name,
        profile_->GetPrefs()->GetBoolean(datatype_pref_name.c_str()));
  }
}

void CredentialCacheService::ScheduleNextReadFromAlternateCredentialCache(
    int delay_secs) {
  DCHECK_LE(0, delay_secs);
  // 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.
  alternate_store_observer_.release();
  alternate_store_.release();
  next_read_.Reset(base::Bind(
      &CredentialCacheService::LookForCachedCredentialsInAlternateProfile,
      weak_factory_.GetWeakPtr()));
  MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      next_read_.callback(),
      TimeDelta::FromSeconds(delay_secs));
}

bool CredentialCacheService::HasPref(scoped_refptr<JsonPrefStore> store,
                                     const std::string& pref_name) {
  return (store->GetValue(pref_name, NULL) == PrefStore::READ_OK);
}

// static
base::StringValue* CredentialCacheService::PackCredential(
    const std::string& credential) {
  // Do nothing for empty credentials.
  if (credential.empty())
    return base::Value::CreateStringValue("");

  browser_sync::ChromeEncryptor encryptor;
  std::string encrypted;
  if (!encryptor.EncryptString(credential, &encrypted)) {
    NOTREACHED();
    return base::Value::CreateStringValue(std::string());
  }

  std::string encoded;
  if (!base::Base64Encode(encrypted, &encoded)) {
    NOTREACHED();
    return base::Value::CreateStringValue(std::string());
  }

  return base::Value::CreateStringValue(encoded);
}

// static
std::string CredentialCacheService::UnpackCredential(
    const base::Value& packed) {
  std::string encoded;
  if (!packed.GetAsString(&encoded)) {
    NOTREACHED();
    return std::string();
  }

  // Do nothing for empty credentials.
  if (encoded.empty())
    return std::string();

  std::string encrypted;
  if (!base::Base64Decode(encoded, &encrypted)) {
    NOTREACHED();
    return std::string();
  }

  browser_sync::ChromeEncryptor encryptor;
  std::string unencrypted;
  if (!encryptor.DecryptString(encrypted, &unencrypted)) {
    NOTREACHED();
    return std::string();
  }

  return unencrypted;
}

void CredentialCacheService::WriteLastCacheUpdateTime() {
  DCHECK(local_store_.get());
  int64 last_cache_update_time_int64 = base::Time::Now().ToInternalValue();
  std::string last_cache_update_time_string =
      base::Int64ToString(last_cache_update_time_int64);
  local_store_->SetValueSilently(
      kLastCacheUpdateTimeKey,
      base::Value::CreateStringValue(last_cache_update_time_string));
}

void CredentialCacheService::PackAndUpdateStringPref(
      const std::string& pref_name,
      const std::string& new_value) {
  DCHECK(local_store_.get());
  if (HasPref(local_store_, pref_name) &&
      GetAndUnpackStringPref(local_store_, pref_name) == new_value) {
    return;
  }
  if (!HasUserSignedOut()) {
    local_store_->SetValueSilently(pref_name, PackCredential(new_value));
  } else {
    // Write a blank value since we cache credentials only for first-time
    // sign-ins.
    local_store_->SetValueSilently(pref_name, PackCredential(std::string()));
  }
  WriteLastCacheUpdateTime();
}

void CredentialCacheService::UpdateBooleanPref(const std::string& pref_name,
                                               bool new_value) {
  DCHECK(local_store_.get());
  if (HasPref(local_store_, pref_name) &&
      GetBooleanPref(local_store_, pref_name) == new_value) {
    return;
  }
  if (!HasUserSignedOut()) {
    local_store_->SetValueSilently(pref_name,
                                   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));
  }
  WriteLastCacheUpdateTime();
}

base::Time CredentialCacheService::GetLastCacheUpdateTime(
    scoped_refptr<JsonPrefStore> store) {
  DCHECK(HasPref(store, kLastCacheUpdateTimeKey));
  const base::Value* last_cache_update_time_value = NULL;
  store->GetValue(kLastCacheUpdateTimeKey, &last_cache_update_time_value);
  DCHECK(last_cache_update_time_value);
  std::string last_cache_update_time_string;
  last_cache_update_time_value->GetAsString(&last_cache_update_time_string);
  int64 last_cache_update_time_int64;
  bool success = base::StringToInt64(last_cache_update_time_string,
                                     &last_cache_update_time_int64);
  DCHECK(success);
  return base::Time::FromInternalValue(last_cache_update_time_int64);
}

bool CredentialCacheService::AlternateCacheIsMoreRecent() {
  DCHECK(alternate_store_.get());
  // If the alternate credential cache doesn't have the "last_cache_update_time"
  // field, it was written by an older version of chrome, and we therefore
  // consider the local cache to be more recent.
  if (!HasPref(alternate_store_, kLastCacheUpdateTimeKey))
    return false;
  DCHECK(HasPref(local_store_, kLastCacheUpdateTimeKey));
  return GetLastCacheUpdateTime(alternate_store_) >
         GetLastCacheUpdateTime(local_store_);
}

std::string CredentialCacheService::GetAndUnpackStringPref(
    scoped_refptr<JsonPrefStore> store,
    const std::string& pref_name) {
  const base::Value* pref_value = NULL;
  store->GetValue(pref_name, &pref_value);
  return UnpackCredential(*pref_value);
}

bool CredentialCacheService::GetBooleanPref(
    scoped_refptr<JsonPrefStore> store,
    const std::string& pref_name) {
  const base::Value* pref_value = NULL;
  store->GetValue(pref_name, &pref_value);
  bool pref;
  pref_value->GetAsBoolean(&pref);
  return pref;
}

CredentialCacheService::LocalStoreObserver::LocalStoreObserver(
    CredentialCacheService* service,
    scoped_refptr<JsonPrefStore> local_store)
    : service_(service),
      local_store_(local_store) {
  local_store_->AddObserver(this);
}

CredentialCacheService::LocalStoreObserver::~LocalStoreObserver() {
  local_store_->RemoveObserver(this);
}

void CredentialCacheService::LocalStoreObserver::OnInitializationCompleted(
    bool succeeded) {
  // Note that |succeeded| will be true even if the local cache file wasn't
  // found, so long as its parent dir (the chrome profile directory) was found.
  // If |succeeded| is false, it means that the chrome profile directory is
  // missing. In this case, there's nothing we can do other than DCHECK.
  DCHECK(succeeded);

  // During startup, we do a precautionary write of the google username,
  // encryption tokens, sync prefs and last cache update time to the local cache
  // in order to recover from the following cases:
  // 1) There is no local credential cache, but the user is signed in. This
  //    could happen if a signed-in user restarts chrome after upgrading from
  //    an older version that didn't support credential caching.
  // 2) There is a local credential cache, but we missed writing sync credential
  //    updates to it in the past due to a crash, or due to the user exiting
  //    chrome in the midst of a sign in, sign out or reconfigure.
  // 3) There is a local credential cache that was written to by an older
  //    version of Chrome, and it does not contain the "last_cache_update_time"
  //    field.
  // Note: If the local credential cache was already up-to-date, the operations
  // below will be no-ops, and won't change the cache's last updated time. Also,
  // if the user is not signed in and there is no local credential cache, we
  // don't want to create a cache with empty values.
  SigninManager* signin =
      SigninManagerFactory::GetForProfile(service_->profile_);
  if ((local_store_->GetReadError() == JsonPrefStore::PREF_READ_ERROR_NO_FILE &&
       !signin->GetAuthenticatedUsername().empty()) ||
      (local_store_->GetReadError() == JsonPrefStore::PREF_READ_ERROR_NONE)) {
    service_->PackAndUpdateStringPref(prefs::kGoogleServicesUsername,
                                      signin->GetAuthenticatedUsername());
    if (!service_->sync_prefs_.GetEncryptionBootstrapToken().empty()) {
      service_->PackAndUpdateStringPref(
          prefs::kSyncEncryptionBootstrapToken,
          service_->sync_prefs_.GetEncryptionBootstrapToken());
    }
    if (!service_->sync_prefs_.GetKeystoreEncryptionBootstrapToken().empty()) {
      service_->PackAndUpdateStringPref(
          prefs::kSyncKeystoreEncryptionBootstrapToken,
          service_->sync_prefs_.GetKeystoreEncryptionBootstrapToken());
    }
    service_->WriteSyncPrefsToLocalCache();
    if (!service_->HasPref(local_store_, kLastCacheUpdateTimeKey))
      service_->WriteLastCacheUpdateTime();
    if (service_->HasPref(local_store_, kLastUpdatedTimeTicksDeprecated))
      local_store_->RemoveValue(kLastUpdatedTimeTicksDeprecated);
  }

  // Now that the local credential cache is ready, start listening for events
  // associated with various sync config changes.
  service_->StartListeningForSyncConfigChanges();
}

void CredentialCacheService::LocalStoreObserver::OnPrefValueChanged(
    const std::string& key) {
  // Nothing to do here, since credentials are cached silently.
}

CredentialCacheService::AlternateStoreObserver::AlternateStoreObserver(
    CredentialCacheService* service,
    scoped_refptr<JsonPrefStore> alternate_store)
    : service_(service),
      alternate_store_(alternate_store) {
  alternate_store_->AddObserver(this);
}

CredentialCacheService::AlternateStoreObserver::~AlternateStoreObserver() {
  alternate_store_->RemoveObserver(this);
}

void CredentialCacheService::AlternateStoreObserver::OnInitializationCompleted(
    bool succeeded) {
  // If an alternate credential cache was found, begin consuming its contents.
  // If not, schedule a future read.
  if (succeeded &&
      alternate_store_->GetReadError() == JsonPrefStore::PREF_READ_ERROR_NONE) {
    service_->ReadCachedCredentialsFromAlternateProfile();
  } else {
    service_->ScheduleNextReadFromAlternateCredentialCache(
        kCredentialCachePollIntervalSecs);
  }
}

void CredentialCacheService::AlternateStoreObserver::OnPrefValueChanged(
    const std::string& key) {
  // Nothing to do here, since credentials are cached silently.
}

FilePath CredentialCacheService::GetCredentialPathInCurrentProfile() const {
  // The sync credential path in the default Desktop profile is
  // "%Appdata%\Local\Google\Chrome\User Data\Default\Sync Credentials", while
  // the sync credential path in the default Metro profile is
  // "%Appdata%\Local\Google\Chrome\Metro\User Data\Default\Sync Credentials".
  DCHECK(profile_);
  return profile_->GetPath().Append(chrome::kSyncCredentialsFilename);
}

FilePath CredentialCacheService::GetCredentialPathInAlternateProfile() const {
  DCHECK(profile_);
  FilePath alternate_user_data_dir;
  chrome::GetAlternateUserDataDirectory(&alternate_user_data_dir);

  // TODO(rsimha): This code path is to allow for testing in the presence of
  // strange singleton mode. Delete this block before shipping.
  // See http://crbug.com/144280.
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kEnableSyncCredentialCaching) &&
      !CredentialCacheServiceFactory::IsDefaultProfile(profile_)) {
    DCHECK(CredentialCacheServiceFactory::IsDefaultAlternateProfileForTest(
               profile_));
    chrome::GetDefaultUserDataDirectory(&alternate_user_data_dir);
  }

  FilePath alternate_default_profile_dir =
      ProfileManager::GetDefaultProfileDir(alternate_user_data_dir);
  return alternate_default_profile_dir.Append(chrome::kSyncCredentialsFilename);
}

void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
  local_store_ = new JsonPrefStore(
      GetCredentialPathInCurrentProfile(),
      content::BrowserThread::GetMessageLoopProxyForThread(
          content::BrowserThread::FILE));
  local_store_observer_ = new LocalStoreObserver(this, local_store_);
  local_store_->ReadPrefsAsync(NULL);
}

void CredentialCacheService::StartListeningForSyncConfigChanges() {
  // Register for notifications for google sign in and sign out.
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT,
                 content::Source<Profile>(profile_));
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
                 content::Source<Profile>(profile_));

  // Register for notifications for sync configuration changes that could occur
  // during sign in or reconfiguration.
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  registrar_.Add(this,
                 chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
                 content::Source<ProfileSyncService>(service));
  registrar_.Add(this,
                 chrome::NOTIFICATION_SYNC_CONFIGURE_START,
                 content::Source<ProfileSyncService>(service));

  // Register for notifications for updates to the sync encryption tokens, which
  // are stored in the PrefStore.
  pref_registrar_.Init(profile_->GetPrefs());
  pref_registrar_.Add(prefs::kSyncEncryptionBootstrapToken, this);
  pref_registrar_.Add(prefs::kSyncKeystoreEncryptionBootstrapToken, this);

  // Register for notifications for updates to lsid and sid, which are stored in
  // the TokenService.
  TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
                 content::Source<TokenService>(token_service));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_SERVICE_CREDENTIALS_UPDATED,
                 content::Source<TokenService>(token_service));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKENS_CLEARED,
                 content::Source<TokenService>(token_service));
}

void CredentialCacheService::LookForCachedCredentialsInAlternateProfile() {
  // Attempt to read cached credentials from the alternate profile. If no file
  // exists, ReadPrefsAsync() will cause PREF_READ_ERROR_NO_FILE to be returned
  // after initialization is complete.
  alternate_store_ = new JsonPrefStore(
      GetCredentialPathInAlternateProfile(),
      content::BrowserThread::GetMessageLoopProxyForThread(
          content::BrowserThread::FILE));
  alternate_store_observer_ = new AlternateStoreObserver(this,
                                                         alternate_store_);
  alternate_store_->ReadPrefsAsync(NULL);
}

bool CredentialCacheService::HasUserSignedOut() {
  DCHECK(local_store_.get());
  // If HasPref() is false, the user never signed in, since there are no
  // previously cached credentials. If the kGoogleServicesUsername pref is
  // empty, it means that the user signed in and subsequently signed out.
  return HasPref(local_store_, prefs::kGoogleServicesUsername) &&
         GetAndUnpackStringPref(local_store_,
                                prefs::kGoogleServicesUsername).empty();
}

void CredentialCacheService::InitiateSignInWithCachedCredentials(
    const std::string& google_services_username,
    const std::string& encryption_bootstrap_token,
    const std::string& keystore_encryption_bootstrap_token,
    bool keep_everything_synced,
    ModelTypeSet preferred_types) {
  // Update the google username in the SigninManager and PrefStore. Also update
  // its value in the local credential cache, since we will not send out
  // NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL in this case.
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  service->signin()->SetAuthenticatedUsername(google_services_username);
  profile_->GetPrefs()->SetString(prefs::kGoogleServicesUsername,
                                  google_services_username);
  PackAndUpdateStringPref(prefs::kGoogleServicesUsername,
                          service->signin()->GetAuthenticatedUsername());

  // Update sync encryption tokens after making sure at least one of them is
  // non-empty.
  DCHECK(!encryption_bootstrap_token.empty() ||
         !keystore_encryption_bootstrap_token.empty());
  if (!encryption_bootstrap_token.empty()) {
    sync_prefs_.SetEncryptionBootstrapToken(encryption_bootstrap_token);
  }
  if (!keystore_encryption_bootstrap_token.empty()) {
    sync_prefs_.SetKeystoreEncryptionBootstrapToken(
        keystore_encryption_bootstrap_token);
  }

  // Update the sync preferences.
  sync_prefs_.SetStartSuppressed(false);
  sync_prefs_.SetSyncSetupCompleted();
  sync_prefs_.SetKeepEverythingSynced(keep_everything_synced);
  sync_prefs_.SetPreferredDataTypes(service->GetRegisteredDataTypes(),
                                    preferred_types);
}

void CredentialCacheService::UpdateTokenServiceCredentials(
    const std::string& alternate_lsid,
    const std::string& alternate_sid) {
  GaiaAuthConsumer::ClientLoginResult login_result;
  login_result.lsid = alternate_lsid;
  login_result.sid = alternate_sid;
  TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
  token_service->UpdateCredentials(login_result);
  DCHECK(token_service->AreCredentialsValid());
  token_service->StartFetchingTokens();
}

void CredentialCacheService::InitiateSignOut() {
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  service->DisableForUser();
}

bool CredentialCacheService::HaveSyncPrefsChanged(
    bool alternate_keep_everything_synced,
    ModelTypeSet alternate_preferred_types) const {
  if (alternate_keep_everything_synced &&
      sync_prefs_.HasKeepEverythingSynced()) {
    return false;
  }
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  ModelTypeSet local_preferred_types =
      sync_prefs_.GetPreferredDataTypes(service->GetRegisteredDataTypes());
  return
      (alternate_keep_everything_synced !=
       sync_prefs_.HasKeepEverythingSynced()) ||
      !alternate_preferred_types.Equals(local_preferred_types);
}

bool CredentialCacheService::HaveSyncEncryptionTokensChanged(
    const std::string& alternate_encryption_bootstrap_token,
    const std::string& alternate_keystore_encryption_bootstrap_token) {
  std::string local_encryption_bootstrap_token;
  if (HasPref(local_store_, prefs::kSyncEncryptionBootstrapToken)) {
    local_encryption_bootstrap_token =
        GetAndUnpackStringPref(local_store_,
                               prefs::kSyncEncryptionBootstrapToken);
  }
  std::string local_keystore_encryption_bootstrap_token;
  if (HasPref(local_store_, prefs::kSyncKeystoreEncryptionBootstrapToken)) {
    local_keystore_encryption_bootstrap_token =
        GetAndUnpackStringPref(local_store_,
                               prefs::kSyncKeystoreEncryptionBootstrapToken);
  }
  return (local_encryption_bootstrap_token !=
          alternate_encryption_bootstrap_token) ||
         (local_keystore_encryption_bootstrap_token !=
          alternate_keystore_encryption_bootstrap_token);
}

bool CredentialCacheService::HaveTokenServiceCredentialsChanged(
    const std::string& alternate_lsid,
    const std::string& alternate_sid) {
  std::string local_lsid =
      GetAndUnpackStringPref(local_store_, GaiaConstants::kGaiaLsid);
  std::string local_sid =
      GetAndUnpackStringPref(local_store_, GaiaConstants::kGaiaSid);
  return local_lsid != alternate_lsid || local_sid != alternate_sid;
}

bool CredentialCacheService::ShouldSignOutOfSync(
    const std::string& alternate_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() &&
         alternate_google_services_username.empty() &&
         !service->setup_in_progress() &&
         AlternateCacheIsMoreRecent();
}

bool CredentialCacheService::MayReconfigureSync(
    const std::string& alternate_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.
  // 6) The sync backend is initialized and ready to consume config changes.
  ProfileSyncService* service =
      ProfileSyncServiceFactory::GetForProfile(profile_);
  return !service->signin()->GetAuthenticatedUsername().empty() &&
         !HasUserSignedOut() &&
         (alternate_google_services_username ==
          service->signin()->GetAuthenticatedUsername()) &&
         !service->setup_in_progress() &&
         AlternateCacheIsMoreRecent() &&
         service->ShouldPushChanges();
}

bool CredentialCacheService::ShouldSignInToSync(
    const std::string& alternate_google_services_username,
    const std::string& alternate_lsid,
    const std::string& alternate_sid,
    const std::string& alternate_encryption_bootstrap_token,
    const std::string& alternate_keystore_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() &&
         !alternate_google_services_username.empty() &&
         !alternate_lsid.empty() &&
         !alternate_sid.empty() &&
         !(alternate_encryption_bootstrap_token.empty() &&
           alternate_keystore_encryption_bootstrap_token.empty()) &&
         !service->setup_in_progress();
}

}  // namespace syncer