blob: e83183122a0d0984bc06b0abf6acce58beaa5b39 [file] [log] [blame]
// Copyright (c) 2011 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/sync_setup_flow.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/sync_setup_flow_handler.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/util/oauth.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/google_service_auth_error.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
namespace {
// Helper function to disable password sync.
void DisablePasswordSync(ProfileSyncService* service) {
syncable::ModelTypeSet types;
service->GetPreferredDataTypes(&types);
types.erase(syncable::PASSWORDS);
service->OnUserChoseDatatypes(false, types);
}
// Returns the next step for the non-fatal error case.
SyncSetupWizard::State GetStepForNonFatalError(ProfileSyncService* service) {
// TODO(sync): Update this error handling to allow different platforms to
// display the error appropriately (http://crbug.com/92722) instead of
// navigating to a LOGIN state that is not supported on every platform.
if (service->IsPassphraseRequired()) {
if (service->IsUsingSecondaryPassphrase())
return SyncSetupWizard::ENTER_PASSPHRASE;
return SyncSetupWizard::GetLoginState();
}
const GoogleServiceAuthError& error = service->GetAuthError();
if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS ||
error.state() == GoogleServiceAuthError::CAPTCHA_REQUIRED ||
error.state() == GoogleServiceAuthError::ACCOUNT_DELETED ||
error.state() == GoogleServiceAuthError::ACCOUNT_DISABLED ||
error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE)
return SyncSetupWizard::GetLoginState();
NOTREACHED();
return SyncSetupWizard::FATAL_ERROR;
}
} // namespace
SyncConfiguration::SyncConfiguration()
: encrypt_all(false),
sync_everything(false),
set_secondary_passphrase(false),
set_gaia_passphrase(false) {
}
SyncConfiguration::~SyncConfiguration() {}
SyncSetupFlow::~SyncSetupFlow() {
flow_handler_->SetFlow(NULL);
}
// static
SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service,
SyncSetupFlowContainer* container,
SyncSetupWizard::State start,
SyncSetupWizard::State end) {
if (start == SyncSetupWizard::NONFATAL_ERROR)
start = GetStepForNonFatalError(service);
if ((start == SyncSetupWizard::CONFIGURE ||
start == SyncSetupWizard::SYNC_EVERYTHING ||
start == SyncSetupWizard::ENTER_PASSPHRASE) &&
!service->sync_initialized()) {
// We are trying to open configuration window, but the backend isn't ready.
// We just return NULL. This has the effect of the flow getting reset, and
// the user's action has no effect.
LOG(ERROR) << "Attempted to show sync configure before backend ready.";
return NULL;
}
return new SyncSetupFlow(start, end, container, service);
}
// static
void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service,
DictionaryValue* args) {
const GoogleServiceAuthError& error = service->GetAuthError();
if (!service->last_attempted_user_email().empty()) {
args->SetString("user", service->last_attempted_user_email());
args->SetInteger("error", error.state());
args->SetBoolean("editable_user", true);
} else {
string16 user;
if (!service->cros_user().empty())
user = UTF8ToUTF16(service->cros_user());
else
user = service->GetAuthenticatedUsername();
args->SetString("user", user);
args->SetInteger("error", 0);
args->SetBoolean("editable_user", user.empty());
}
args->SetString("captchaUrl", error.captcha().image_url.spec());
}
void SyncSetupFlow::GetArgsForConfigure(ProfileSyncService* service,
DictionaryValue* args) {
// The SYNC_EVERYTHING case will set this to true.
args->SetBoolean("showSyncEverythingPage", false);
args->SetBoolean("syncAllDataTypes",
service->profile()->GetPrefs()->GetBoolean(
prefs::kSyncKeepEverythingSynced));
// Bookmarks, Preferences, and Themes are launched for good, there's no
// going back now. Check if the other data types are registered though.
syncable::ModelTypeSet registered_types;
service->GetRegisteredDataTypes(&registered_types);
args->SetBoolean("passwordsRegistered",
registered_types.count(syncable::PASSWORDS) > 0);
args->SetBoolean("autofillRegistered",
registered_types.count(syncable::AUTOFILL) > 0);
args->SetBoolean("extensionsRegistered",
registered_types.count(syncable::EXTENSIONS) > 0);
args->SetBoolean("typedUrlsRegistered",
registered_types.count(syncable::TYPED_URLS) > 0);
args->SetBoolean("appsRegistered",
registered_types.count(syncable::APPS) > 0);
args->SetBoolean("searchEnginesRegistered",
registered_types.count(syncable::SEARCH_ENGINES) > 0);
args->SetBoolean("sessionsRegistered",
registered_types.count(syncable::SESSIONS) > 0);
args->SetBoolean("syncBookmarks",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks));
args->SetBoolean("syncPreferences",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences));
args->SetBoolean("syncThemes",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes));
args->SetBoolean("syncPasswords",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords));
args->SetBoolean("syncAutofill",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill));
args->SetBoolean("syncExtensions",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions));
args->SetBoolean("syncSearchEngines",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSearchEngines));
args->SetBoolean("syncSessions",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions));
args->SetBoolean("syncTypedUrls",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls));
args->SetBoolean("syncApps",
service->profile()->GetPrefs()->GetBoolean(prefs::kSyncApps));
args->SetBoolean("encryptionEnabled",
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableSyncEncryption));
bool encrypt_all = service->EncryptEverythingEnabled();
if (service->encryption_pending())
encrypt_all = true;
args->SetBoolean("encryptAllData", encrypt_all);
// Load the parameters for the encryption tab.
args->SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase());
// Determine if we need a passphrase or not, and if so, prompt the user.
if (service->IsPassphraseRequiredForDecryption() &&
(service->IsUsingSecondaryPassphrase() || cached_passphrase_.empty())) {
// We need a passphrase, and either it's an explicit passphrase, or we
// don't have a cached gaia passphrase, so we have to prompt the user.
args->SetBoolean("show_passphrase", true);
// Tell the UI layer what kind of passphrase we need.
args->SetBoolean("need_google_passphrase",
!service->IsUsingSecondaryPassphrase());
args->SetBoolean("passphrase_creation_rejected",
user_tried_creating_explicit_passphrase_);
args->SetBoolean("passphrase_setting_rejected",
user_tried_setting_passphrase_);
}
}
bool SyncSetupFlow::AttachSyncSetupHandler(SyncSetupFlowHandler* handler) {
if (flow_handler_)
return false;
flow_handler_ = handler;
handler->SetFlow(this);
ActivateState(current_state_);
return true;
}
bool SyncSetupFlow::IsAttached() const {
return flow_handler_ != NULL;
}
void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) {
if (!ShouldAdvance(advance_state)) {
LOG(WARNING) << "Invalid state change from "
<< current_state_ << " to " << advance_state;
return;
}
if (flow_handler_)
ActivateState(advance_state);
}
void SyncSetupFlow::Focus() {
// This gets called from SyncSetupWizard::Focus(), and might get called
// before flow_handler_ is set in AttachSyncSetupHandler() (which gets
// called asynchronously after the UI initializes).
if (flow_handler_)
flow_handler_->Focus();
}
// A callback to notify the delegate that the dialog closed.
// TODO(rickcam): Bug 90713: Handle OAUTH_LOGIN case here
void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) {
DCHECK(json_retval.empty());
container_->set_flow(NULL); // Sever ties from the wizard.
// If we've reached the end, mark it. This could be a discrete run, in which
// case it's already set, but it simplifes the logic to do it this way.
if (current_state_ == end_state_)
service_->SetSyncSetupCompleted();
// Record the state at which the user cancelled the signon dialog.
switch (current_state_) {
case SyncSetupWizard::GAIA_LOGIN:
ProfileSyncService::SyncEvent(
ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
break;
case SyncSetupWizard::GAIA_SUCCESS:
ProfileSyncService::SyncEvent(
ProfileSyncService::CANCEL_DURING_SIGNON);
break;
case SyncSetupWizard::CONFIGURE:
case SyncSetupWizard::ENTER_PASSPHRASE:
case SyncSetupWizard::SETTING_UP:
// TODO(atwilson): Treat a close during ENTER_PASSPHRASE like a
// Cancel + Skip (i.e. call OnPassphraseCancel()). http://crbug.com/74645
ProfileSyncService::SyncEvent(
ProfileSyncService::CANCEL_DURING_CONFIGURE);
break;
case SyncSetupWizard::DONE:
// TODO(sync): rename this histogram; it's tracking authorization AND
// initial sync download time.
UMA_HISTOGRAM_MEDIUM_TIMES("Sync.UserPerceivedAuthorizationTime",
base::TimeTicks::Now() - login_start_time_);
break;
default:
break;
}
service_->OnUserCancelledDialog();
delete this;
}
void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username,
const std::string& password,
const std::string& captcha,
const std::string& access_code) {
// It's possible to receive an empty password (e.g. for ASP's), in which case
// we don't want to overwrite any previously cached password.
if (!password.empty())
cached_passphrase_ = password;
service_->OnUserSubmittedAuth(username, password, captcha, access_code);
}
void SyncSetupFlow::OnUserSubmittedOAuth(
const std::string& oauth1_request_token) {
service_->OnUserSubmittedOAuth(oauth1_request_token);
}
void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) {
// Go to the "loading..." screen.
Advance(SyncSetupWizard::SETTING_UP);
// Note: encryption will not occur until OnUserChoseDatatypes is called.
service_->SetEncryptEverything(configuration.encrypt_all);
bool set_new_decryption_passphrase = false;
if (configuration.set_gaia_passphrase) {
// Caller passed a gaia passphrase. This is illegal if we are currently
// using a secondary passphrase.
DCHECK(!service_->IsUsingSecondaryPassphrase());
service_->SetPassphrase(configuration.gaia_passphrase, false);
// Since the user entered the passphrase manually, set this flag so we can
// report an error if the passphrase setting failed.
user_tried_setting_passphrase_ = true;
set_new_decryption_passphrase = true;
} else if (!service_->IsUsingSecondaryPassphrase() &&
!cached_passphrase_.empty()) {
// Service needs a GAIA passphrase and we have one cached, so try it.
service_->SetPassphrase(cached_passphrase_, false);
cached_passphrase_.clear();
set_new_decryption_passphrase = true;
} else {
// We can get here if the user changes their GAIA passphrase but still has
// data encrypted with the old passphrase. The UI prompts the user for their
// passphrase, but they might just leave it blank/disable the encrypted
// types.
// No gaia passphrase cached or set, so make sure the ProfileSyncService
// wasn't expecting one.
DLOG_IF(WARNING, !service_->IsUsingSecondaryPassphrase() &&
service_->IsPassphraseRequiredForDecryption()) <<
"Google passphrase required but not provided by UI";
}
// Set the secondary passphrase, either as a decryption passphrase, or
// as an attempt to encrypt the user's data using this new passphrase.
if (configuration.set_secondary_passphrase) {
service_->SetPassphrase(configuration.secondary_passphrase, true);
if (service_->IsUsingSecondaryPassphrase()) {
user_tried_setting_passphrase_ = true;
set_new_decryption_passphrase = true;
} else {
user_tried_creating_explicit_passphrase_ = true;
}
}
service_->OnUserChoseDatatypes(configuration.sync_everything,
configuration.data_types);
// See if we are done configuring (if we don't need a passphrase, and don't
// need to hang around waiting for encryption to happen, just exit). This call
// to IsPassphraseRequiredForDecryption() takes into account the data types
// we just enabled/disabled.
if (!service_->IsPassphraseRequiredForDecryption() &&
!service_->encryption_pending()) {
Advance(SyncSetupWizard::DONE);
} else if (!set_new_decryption_passphrase) {
// We need a passphrase, but the user did not provide one, so transition
// directly to ENTER_PASSPHRASE (otherwise we'll have to wait until
// the sync engine generates another OnPassphraseRequired() at the end of
// the sync cycle which can take a long time).
Advance(SyncSetupWizard::ENTER_PASSPHRASE);
}
}
void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase) {
Advance(SyncSetupWizard::SETTING_UP);
service_->SetPassphrase(passphrase, true);
user_tried_setting_passphrase_ = true;
}
void SyncSetupFlow::OnPassphraseCancel() {
// If the user cancels when being asked for the passphrase,
// just disable encrypted sync and continue setting up.
if (current_state_ == SyncSetupWizard::ENTER_PASSPHRASE)
DisablePasswordSync(service_);
Advance(SyncSetupWizard::SETTING_UP);
}
// Use static Run method to get an instance.
SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state,
SyncSetupWizard::State end_state,
SyncSetupFlowContainer* container,
ProfileSyncService* service)
: container_(container),
current_state_(start_state),
end_state_(end_state),
login_start_time_(base::TimeTicks::Now()),
flow_handler_(NULL),
service_(service),
user_tried_creating_explicit_passphrase_(false),
user_tried_setting_passphrase_(false) {
}
// Returns true if the flow should advance to |state| based on |current_state_|.
bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) {
switch (state) {
case SyncSetupWizard::OAUTH_LOGIN:
return current_state_ == SyncSetupWizard::FATAL_ERROR ||
current_state_ == SyncSetupWizard::OAUTH_LOGIN ||
current_state_ == SyncSetupWizard::SETTING_UP;
case SyncSetupWizard::GAIA_LOGIN:
return current_state_ == SyncSetupWizard::FATAL_ERROR ||
current_state_ == SyncSetupWizard::GAIA_LOGIN ||
current_state_ == SyncSetupWizard::SETTING_UP;
case SyncSetupWizard::GAIA_SUCCESS:
return current_state_ == SyncSetupWizard::GAIA_LOGIN ||
current_state_ == SyncSetupWizard::OAUTH_LOGIN;
case SyncSetupWizard::SYNC_EVERYTHING: // Intentionally fall through.
case SyncSetupWizard::CONFIGURE:
return current_state_ == SyncSetupWizard::GAIA_SUCCESS;
case SyncSetupWizard::ENTER_PASSPHRASE:
return current_state_ == SyncSetupWizard::SYNC_EVERYTHING ||
current_state_ == SyncSetupWizard::CONFIGURE ||
current_state_ == SyncSetupWizard::SETTING_UP;
case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR:
return current_state_ != SyncSetupWizard::ABORT;
case SyncSetupWizard::SETTING_UP:
return current_state_ == SyncSetupWizard::SYNC_EVERYTHING ||
current_state_ == SyncSetupWizard::CONFIGURE ||
current_state_ == SyncSetupWizard::ENTER_PASSPHRASE;
case SyncSetupWizard::NONFATAL_ERROR: // Intentionally fall through.
case SyncSetupWizard::FATAL_ERROR:
return current_state_ != SyncSetupWizard::ABORT;
case SyncSetupWizard::ABORT:
return true;
case SyncSetupWizard::DONE:
return current_state_ == SyncSetupWizard::SETTING_UP ||
current_state_ == SyncSetupWizard::ENTER_PASSPHRASE;
default:
NOTREACHED() << "Unhandled State: " << state;
return false;
}
}
void SyncSetupFlow::ActivateState(SyncSetupWizard::State state) {
DCHECK(flow_handler_);
if (state == SyncSetupWizard::NONFATAL_ERROR)
state = GetStepForNonFatalError(service_);
current_state_ = state;
switch (state) {
case SyncSetupWizard::OAUTH_LOGIN: {
flow_handler_->ShowOAuthLogin();
break;
}
case SyncSetupWizard::GAIA_LOGIN: {
DictionaryValue args;
SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
flow_handler_->ShowGaiaLogin(args);
break;
}
case SyncSetupWizard::GAIA_SUCCESS:
if (end_state_ == SyncSetupWizard::GAIA_SUCCESS) {
flow_handler_->ShowGaiaSuccessAndClose();
break;
}
flow_handler_->ShowGaiaSuccessAndSettingUp();
break;
case SyncSetupWizard::SYNC_EVERYTHING: {
DictionaryValue args;
SyncSetupFlow::GetArgsForConfigure(service_, &args);
args.SetBoolean("showSyncEverythingPage", true);
flow_handler_->ShowConfigure(args);
break;
}
case SyncSetupWizard::CONFIGURE: {
DictionaryValue args;
SyncSetupFlow::GetArgsForConfigure(service_, &args);
flow_handler_->ShowConfigure(args);
break;
}
case SyncSetupWizard::ENTER_PASSPHRASE: {
DictionaryValue args;
SyncSetupFlow::GetArgsForConfigure(service_, &args);
GetArgsForConfigure(service_, &args);
// TODO(atwilson): Remove ShowPassphraseEntry in favor of using
// ShowConfigure() - http://crbug.com/90786.
flow_handler_->ShowPassphraseEntry(args);
break;
}
case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: {
// TODO(sync): We should expose a real "display an error" API on
// SyncSetupFlowHandler (crbug.com/92722) but for now just transition
// to the login state with a special error code.
DictionaryValue args;
SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
args.SetInteger("error", GoogleServiceAuthError::SERVICE_UNAVAILABLE);
current_state_ = SyncSetupWizard::GAIA_LOGIN;
flow_handler_->ShowGaiaLogin(args);
break;
}
case SyncSetupWizard::SETTING_UP: {
flow_handler_->ShowSettingUp();
break;
}
case SyncSetupWizard::FATAL_ERROR: {
// This shows the user the "Could not connect to server" error.
// TODO(sync): Update this error handling to allow different platforms to
// display the error appropriately (http://crbug.com/92722).
DictionaryValue args;
SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
args.SetBoolean("fatalError", true);
current_state_ = SyncSetupWizard::GAIA_LOGIN;
flow_handler_->ShowGaiaLogin(args);
break;
}
case SyncSetupWizard::DONE:
case SyncSetupWizard::ABORT:
flow_handler_->ShowSetupDone(
UTF16ToWide(service_->GetAuthenticatedUsername()));
break;
default:
NOTREACHED() << "Invalid advance state: " << state;
}
}