| // 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/ui/webui/sync_setup_handler.h" |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/google/google_util.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_info_cache.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_metrics.h" |
| #include "chrome/browser/signin/signin_manager.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/webui/signin/login_ui_service.h" |
| #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" |
| #include "chrome/browser/ui/webui/sync_promo/sync_promo_ui.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/net/gaia/gaia_constants.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/locale_settings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using content::WebContents; |
| using l10n_util::GetStringFUTF16; |
| using l10n_util::GetStringUTF16; |
| |
| namespace { |
| |
| // A structure which contains all the configuration information for sync. |
| struct SyncConfigInfo { |
| SyncConfigInfo(); |
| ~SyncConfigInfo(); |
| |
| bool encrypt_all; |
| bool sync_everything; |
| syncer::ModelTypeSet data_types; |
| std::string passphrase; |
| bool passphrase_is_gaia; |
| }; |
| |
| SyncConfigInfo::SyncConfigInfo() |
| : encrypt_all(false), |
| sync_everything(false), |
| passphrase_is_gaia(false) { |
| } |
| |
| SyncConfigInfo::~SyncConfigInfo() {} |
| |
| const char* kDataTypeNames[] = { |
| "apps", |
| "autofill", |
| "bookmarks", |
| "extensions", |
| "passwords", |
| "preferences", |
| "sessions", |
| "themes", |
| "typedUrls" |
| }; |
| |
| const syncer::ModelType kDataTypes[] = { |
| syncer::APPS, |
| syncer::AUTOFILL, |
| syncer::BOOKMARKS, |
| syncer::EXTENSIONS, |
| syncer::PASSWORDS, |
| syncer::PREFERENCES, |
| syncer::SESSIONS, |
| syncer::THEMES, |
| syncer::TYPED_URLS |
| }; |
| |
| static const size_t kNumDataTypes = arraysize(kDataTypes); |
| COMPILE_ASSERT(arraysize(kDataTypeNames) == arraysize(kDataTypes), |
| kDataTypes_does_not_match_kDataTypeNames); |
| |
| static const char kDefaultSigninDomain[] = "gmail.com"; |
| |
| bool GetAuthData(const std::string& json, |
| std::string* username, |
| std::string* password, |
| std::string* captcha, |
| std::string* otp, |
| std::string* access_code) { |
| scoped_ptr<Value> parsed_value(base::JSONReader::Read(json)); |
| if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) |
| return false; |
| |
| DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); |
| if (!result->GetString("user", username) || |
| !result->GetString("pass", password) || |
| !result->GetString("captcha", captcha) || |
| !result->GetString("otp", otp) || |
| !result->GetString("accessCode", access_code)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool GetConfiguration(const std::string& json, SyncConfigInfo* config) { |
| scoped_ptr<Value> parsed_value(base::JSONReader::Read(json)); |
| DictionaryValue* result; |
| if (!parsed_value.get() || !parsed_value->GetAsDictionary(&result)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a Dictionary"; |
| return false; |
| } |
| |
| if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value"; |
| return false; |
| } |
| |
| for (size_t i = 0; i < arraysize(kDataTypeNames); ++i) { |
| std::string key_name = kDataTypeNames[i] + std::string("Synced"); |
| bool sync_value; |
| if (!result->GetBoolean(key_name, &sync_value)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name; |
| return false; |
| } |
| if (sync_value) |
| config->data_types.Put(kDataTypes[i]); |
| } |
| |
| // Encryption settings. |
| if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData"; |
| return false; |
| } |
| |
| // Passphrase settings. |
| bool have_passphrase; |
| if (!result->GetBoolean("usePassphrase", &have_passphrase)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value"; |
| return false; |
| } |
| |
| if (have_passphrase) { |
| if (!result->GetBoolean("isGooglePassphrase", |
| &config->passphrase_is_gaia)) { |
| DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value"; |
| return false; |
| } |
| if (!result->GetString("passphrase", &config->passphrase)) { |
| DLOG(ERROR) << "GetConfiguration() not passed a passphrase value"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool GetPassphrase(const std::string& json, std::string* passphrase) { |
| scoped_ptr<Value> parsed_value(base::JSONReader::Read(json)); |
| if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) |
| return false; |
| |
| DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); |
| return result->GetString("passphrase", passphrase); |
| } |
| |
| string16 NormalizeUserName(const string16& user) { |
| if (user.find_first_of(ASCIIToUTF16("@")) != string16::npos) |
| return user; |
| return user + ASCIIToUTF16("@") + ASCIIToUTF16(kDefaultSigninDomain); |
| } |
| |
| bool AreUserNamesEqual(const string16& user1, const string16& user2) { |
| return NormalizeUserName(user1) == NormalizeUserName(user2); |
| } |
| |
| bool IsClientOAuthEnabled() { |
| return !CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableClientOAuthSignin); |
| } |
| |
| } // namespace |
| |
| SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager) |
| : configuring_sync_(false), |
| profile_manager_(profile_manager), |
| last_signin_error_(GoogleServiceAuthError::NONE), |
| retry_on_signin_failure_(true) { |
| } |
| |
| SyncSetupHandler::~SyncSetupHandler() { |
| // Just exit if running unit tests (no actual WebUI is attached). |
| if (!web_ui()) |
| return; |
| |
| // This case is hit when the user performs a back navigation. |
| CloseSyncSetup(); |
| } |
| |
| void SyncSetupHandler::GetLocalizedValues(DictionaryValue* localized_strings) { |
| GetStaticLocalizedValues(localized_strings, web_ui()); |
| } |
| |
| void SyncSetupHandler::GetStaticLocalizedValues( |
| DictionaryValue* localized_strings, |
| content::WebUI* web_ui) { |
| DCHECK(localized_strings); |
| |
| localized_strings->SetString( |
| "invalidPasswordHelpURL", chrome::kInvalidPasswordHelpURL); |
| localized_strings->SetString( |
| "cannotAccessAccountURL", chrome::kCanNotAccessAccountURL); |
| string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME)); |
| localized_strings->SetString( |
| "introduction", |
| GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION, product_name)); |
| localized_strings->SetString( |
| "chooseDataTypesInstructions", |
| GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name)); |
| localized_strings->SetString( |
| "encryptionInstructions", |
| GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name)); |
| localized_strings->SetString( |
| "encryptionHelpURL", chrome::kSyncEncryptionHelpURL); |
| localized_strings->SetString( |
| "passphraseEncryptionMessage", |
| GetStringFUTF16(IDS_SYNC_PASSPHRASE_ENCRYPTION_MESSAGE, product_name)); |
| localized_strings->SetString( |
| "passphraseRecover", |
| GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER, |
| ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam( |
| chrome::kSyncGoogleDashboardURL)))); |
| localized_strings->SetString("stopSyncingExplanation", |
| l10n_util::GetStringFUTF16( |
| IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL, |
| l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), |
| ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam( |
| chrome::kSyncGoogleDashboardURL)))); |
| localized_strings->SetString("stopSyncingTitle", |
| l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE)); |
| localized_strings->SetString("stopSyncingConfirm", |
| l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL)); |
| |
| bool is_start_page = false; |
| if (web_ui) { |
| SyncPromoUI::Source source = SyncPromoUI::GetSourceForSyncPromoURL( |
| web_ui->GetWebContents()->GetURL()); |
| is_start_page = source == SyncPromoUI::SOURCE_START_PAGE; |
| } |
| int title_id = is_start_page ? IDS_SYNC_PROMO_TITLE_SHORT : |
| IDS_SYNC_PROMO_TITLE_EXISTING_USER; |
| string16 short_product_name(GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); |
| localized_strings->SetString( |
| "promoTitle", GetStringFUTF16(title_id, short_product_name)); |
| |
| localized_strings->SetString( |
| "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL); |
| localized_strings->SetString( |
| "syncErrorHelpURL", chrome::kSyncErrorsHelpURL); |
| |
| std::string create_account_url = google_util::StringAppendGoogleLocaleParam( |
| chrome::kSyncCreateNewAccountURL); |
| string16 create_account = GetStringUTF16(IDS_SYNC_CREATE_ACCOUNT); |
| create_account= UTF8ToUTF16("<a id='create-account-link' target='_blank' " |
| "class='account-link' href='" + create_account_url + "'>") + |
| create_account + UTF8ToUTF16("</a>"); |
| localized_strings->SetString("createAccountLinkHTML", |
| GetStringFUTF16(IDS_SYNC_CREATE_ACCOUNT_PREFIX, create_account)); |
| |
| string16 sync_benefits_url( |
| UTF8ToUTF16(google_util::StringAppendGoogleLocaleParam( |
| chrome::kSyncLearnMoreURL))); |
| localized_strings->SetString("promoLearnMoreURL", sync_benefits_url); |
| |
| static OptionsStringResource resources[] = { |
| { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE }, |
| { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE }, |
| { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT }, |
| { "cannotBeBlank", IDS_SYNC_CANNOT_BE_BLANK }, |
| { "emailLabel", IDS_SYNC_LOGIN_EMAIL_NEW_LINE }, |
| { "passwordLabel", IDS_SYNC_LOGIN_PASSWORD_NEW_LINE }, |
| { "invalidCredentials", IDS_SYNC_INVALID_USER_CREDENTIALS }, |
| { "signin", IDS_SYNC_SIGNIN }, |
| { "couldNotConnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT }, |
| { "unrecoverableError", IDS_SYNC_UNRECOVERABLE_ERROR }, |
| { "errorLearnMore", IDS_LEARN_MORE }, |
| { "unrecoverableErrorHelpURL", IDS_SYNC_UNRECOVERABLE_ERROR_HELP_URL }, |
| { "cannotAccessAccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT }, |
| { "cancel", IDS_CANCEL }, |
| { "loginSuccess", IDS_SYNC_SUCCESS }, |
| { "settingUp", IDS_SYNC_LOGIN_SETTING_UP }, |
| { "errorSigningIn", IDS_SYNC_ERROR_SIGNING_IN }, |
| { "signinHeader", IDS_SYNC_PROMO_SIGNIN_HEADER}, |
| { "captchaInstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS }, |
| { "invalidAccessCode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL }, |
| { "enterAccessCode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL }, |
| { "getAccessCodeHelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL }, |
| { "getAccessCodeURL", IDS_SYNC_GET_ACCESS_CODE_URL }, |
| { "invalidOtp", IDS_SYNC_INVALID_OTP_LABEL }, |
| { "enterOtp", IDS_SYNC_ENTER_OTP_LABEL }, |
| { "getOtpHelp", IDS_SYNC_OTP_HELP_LABEL }, |
| { "getOtpURL", IDS_SYNC_GET_OTP_URL }, |
| { "syncAllDataTypes", IDS_SYNC_EVERYTHING }, |
| { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES }, |
| { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS }, |
| { "preferences", IDS_SYNC_DATATYPE_PREFERENCES }, |
| { "autofill", IDS_SYNC_DATATYPE_AUTOFILL }, |
| { "themes", IDS_SYNC_DATATYPE_THEMES }, |
| { "passwords", IDS_SYNC_DATATYPE_PASSWORDS }, |
| { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS }, |
| { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS }, |
| { "apps", IDS_SYNC_DATATYPE_APPS }, |
| { "openTabs", IDS_SYNC_DATATYPE_TABS }, |
| { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR }, |
| { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR }, |
| { "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE }, |
| { "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT }, |
| { "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE }, |
| { "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT }, |
| { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL }, |
| { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL }, |
| { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR }, |
| { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR }, |
| { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING }, |
| { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL }, |
| { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES }, |
| { "syncEverything", IDS_SYNC_SYNC_EVERYTHING }, |
| { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS }, |
| { "passphraseSectionTitle", IDS_SYNC_PASSPHRASE_SECTION_TITLE }, |
| { "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE }, |
| { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY }, |
| { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY }, |
| { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL }, |
| { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE }, |
| { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING }, |
| { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES }, |
| { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO }, |
| { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX }, |
| { "sectionExplicitMessagePostfix", |
| IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX }, |
| { "encryptedDataTypesTitle", IDS_SYNC_ENCRYPTION_DATA_TYPES_TITLE }, |
| { "encryptSensitiveOption", IDS_SYNC_ENCRYPT_SENSITIVE_DATA }, |
| { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA }, |
| { "aspWarningText", IDS_SYNC_ASP_PASSWORD_WARNING_TEXT }, |
| { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE }, |
| { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON }, |
| { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED }, |
| { "promoLearnMore", IDS_LEARN_MORE }, |
| { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT }, |
| }; |
| |
| RegisterStrings(localized_strings, resources, arraysize(resources)); |
| RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE); |
| } |
| |
| void SyncSetupHandler::DisplayConfigureSync(bool show_advanced, |
| bool passphrase_failed) { |
| ProfileSyncService* service = GetSyncService(); |
| if (!service->sync_initialized()) { |
| // When user tries to setup sync while the sync backend is not initialized, |
| // kick the sync backend and wait for it to be ready and show spinner until |
| // the backend gets ready. |
| retry_on_signin_failure_ = false; |
| |
| service->UnsuppressAndStart(); |
| DisplaySpinner(); |
| // To listen to the token available notifications, start SigninTracker. |
| signin_tracker_.reset( |
| new SigninTracker(GetProfile(), this, |
| SigninTracker::SERVICES_INITIALIZING)); |
| return; |
| } |
| |
| // Should only be called if user is signed in, so no longer need our |
| // SigninTracker. |
| signin_tracker_.reset(); |
| configuring_sync_ = true; |
| DCHECK(service->sync_initialized()) << |
| "Cannot configure sync until the sync backend is initialized"; |
| |
| // Setup args for the sync configure screen: |
| // showSyncEverythingPage: false to skip directly to the configure screen |
| // syncAllDataTypes: true if the user wants to sync everything |
| // <data_type>Registered: true if the associated data type is supported |
| // <data_type>Synced: true if the user wants to sync that specific data type |
| // encryptionEnabled: true if sync supports encryption |
| // encryptAllData: true if user wants to encrypt all data (not just |
| // passwords) |
| // usePassphrase: true if the data is encrypted with a secondary passphrase |
| // show_passphrase: true if a passphrase is needed to decrypt the sync data |
| DictionaryValue args; |
| |
| // Tell the UI layer which data types are registered/enabled by the user. |
| const syncer::ModelTypeSet registered_types = |
| service->GetRegisteredDataTypes(); |
| const syncer::ModelTypeSet preferred_types = |
| service->GetPreferredDataTypes(); |
| for (size_t i = 0; i < kNumDataTypes; ++i) { |
| const std::string key_name = kDataTypeNames[i]; |
| args.SetBoolean(key_name + "Registered", |
| registered_types.Has(kDataTypes[i])); |
| args.SetBoolean(key_name + "Synced", preferred_types.Has(kDataTypes[i])); |
| } |
| browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs()); |
| args.SetBoolean("passphraseFailed", passphrase_failed); |
| args.SetBoolean("showSyncEverythingPage", !show_advanced); |
| args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced()); |
| args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled()); |
| args.SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase()); |
| // We call IsPassphraseRequired() here, instead of calling |
| // IsPassphraseRequiredForDecryption(), because we want to show the passphrase |
| // UI even if no encrypted data types are enabled. |
| args.SetBoolean("showPassphrase", service->IsPassphraseRequired()); |
| |
| StringValue page("configure"); |
| web_ui()->CallJavascriptFunction( |
| "SyncSetupOverlay.showSyncSetupPage", page, args); |
| } |
| |
| void SyncSetupHandler::ConfigureSyncDone() { |
| StringValue page("done"); |
| web_ui()->CallJavascriptFunction( |
| "SyncSetupOverlay.showSyncSetupPage", page); |
| |
| // Suppress the sync promo once the user signs into sync. This way the user |
| // doesn't see the sync promo even if they sign out of sync later on. |
| SyncPromoUI::SetUserSkippedSyncPromo(GetProfile()); |
| |
| ProfileSyncService* service = GetSyncService(); |
| if (!service->HasSyncSetupCompleted()) { |
| // This is the first time configuring sync, so log it. |
| FilePath profile_file_path = GetProfile()->GetPath(); |
| ProfileMetrics::LogProfileSyncSignIn(profile_file_path); |
| |
| // We're done configuring, so notify ProfileSyncService that it is OK to |
| // start syncing. |
| service->SetSyncSetupCompleted(); |
| } |
| } |
| |
| bool SyncSetupHandler::IsActiveLogin() const { |
| // LoginUIService can be NULL if page is brought up in incognito mode |
| // (i.e. if the user is running in guest mode in cros and brings up settings). |
| LoginUIService* service = GetLoginUIService(); |
| return service && (service->current_login_ui() == this); |
| } |
| |
| void SyncSetupHandler::RegisterMessages() { |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupDidClosePage", |
| base::Bind(&SyncSetupHandler::OnDidClosePage, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupSubmitAuth", |
| base::Bind(&SyncSetupHandler::HandleSubmitAuth, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupConfigure", |
| base::Bind(&SyncSetupHandler::HandleConfigure, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupAttachHandler", |
| base::Bind(&SyncSetupHandler::HandleAttachHandler, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupShowErrorUI", |
| base::Bind(&SyncSetupHandler::HandleShowErrorUI, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupShowSetupUI", |
| base::Bind(&SyncSetupHandler::HandleShowSetupUI, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupShowSetupUIWithoutLogin", |
| base::Bind(&SyncSetupHandler::HandleShowSetupUIWithoutLogin, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "SyncSetupDoSignOutOnAuthError", |
| base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback("CloseTimeout", |
| base::Bind(&SyncSetupHandler::HandleCloseTimeout, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback("SyncSetupStopSyncing", |
| base::Bind(&SyncSetupHandler::HandleStopSyncing, |
| base::Unretained(this))); |
| } |
| |
| SigninManager* SyncSetupHandler::GetSignin() const { |
| return SigninManagerFactory::GetForProfile(GetProfile()); |
| } |
| |
| void SyncSetupHandler::DisplayGaiaLogin(bool fatal_error) { |
| retry_on_signin_failure_ = true; |
| DisplayGaiaLoginWithErrorMessage(string16(), fatal_error); |
| } |
| |
| void SyncSetupHandler::DisplayGaiaLoginWithErrorMessage( |
| const string16& error_message, bool fatal_error) { |
| // We are no longer configuring sync if the login screen is visible. |
| // If the user exits the signin wizard after this without configuring sync, |
| // CloseSyncSetup() will ensure they are logged out. |
| configuring_sync_ = false; |
| |
| string16 local_error_message(error_message); |
| |
| // Setup args for the GAIA login screen: |
| // error_message: custom error message to display. |
| // fatalError: fatal error message to display. |
| // error: GoogleServiceAuthError from previous login attempt (0 if none). |
| // user: The email the user most recently entered. |
| // editable_user: Whether the username field should be editable. |
| // captchaUrl: The captcha image to display to the user (empty if none). |
| std::string user, captcha; |
| int error; |
| bool editable_user; |
| if (!last_attempted_user_email_.empty()) { |
| // This is a repeat of a login attempt. |
| user = last_attempted_user_email_; |
| error = last_signin_error_.state(); |
| captcha = last_signin_error_.captcha().image_url.spec(); |
| editable_user = true; |
| |
| if (local_error_message.empty()) |
| local_error_message = UTF8ToUTF16(last_signin_error_.error_message()); |
| } else { |
| // Fresh login attempt - lock in the authenticated username if there is |
| // one (don't let the user change it). |
| user = GetSignin()->GetAuthenticatedUsername(); |
| error = 0; |
| editable_user = user.empty(); |
| } |
| DictionaryValue args; |
| args.SetString("user", user); |
| args.SetInteger("error", error); |
| |
| // If the error is two-factor, then ask for an OTP if the ClientOAuth flow |
| // is enasbled. Otherwise ask for an ASP. If the error is catptcha required, |
| // then we don't want to show username and password fields if ClientOAuth is |
| // being used, since those fields are ignored by the endpoint on challenges. |
| if (error == GoogleServiceAuthError::TWO_FACTOR) |
| args.SetBoolean("askForOtp", IsClientOAuthEnabled()); |
| else if (error == GoogleServiceAuthError::CAPTCHA_REQUIRED) |
| args.SetBoolean("hideEmailAndPassword", IsClientOAuthEnabled()); |
| |
| args.SetBoolean("editableUser", editable_user); |
| if (!local_error_message.empty()) |
| args.SetString("errorMessage", local_error_message); |
| if (fatal_error) |
| args.SetBoolean("fatalError", true); |
| args.SetString("captchaUrl", captcha); |
| StringValue page("login"); |
| web_ui()->CallJavascriptFunction( |
| "SyncSetupOverlay.showSyncSetupPage", page, args); |
| } |
| |
| bool SyncSetupHandler::PrepareSyncSetup() { |
| ProfileSyncService* service = GetSyncService(); |
| if (!service) { |
| // If there's no sync service, the user tried to manually invoke a syncSetup |
| // URL, but sync features are disabled. We need to close the overlay for |
| // this (rare) case. |
| DLOG(WARNING) << "Closing sync UI because sync is disabled"; |
| CloseOverlay(); |
| return false; |
| } |
| |
| // If the wizard is already visible, just focus that one. |
| if (FocusExistingWizardIfPresent()) { |
| if (!IsActiveLogin()) |
| CloseOverlay(); |
| return false; |
| } |
| |
| // Notify services that login UI is now active. |
| GetLoginUIService()->SetLoginUI(this); |
| service->SetSetupInProgress(true); |
| |
| return true; |
| } |
| |
| // TODO(kochi): Handle error conditions (timeout, other failures). |
| // http://crbug.com/128692 |
| void SyncSetupHandler::DisplaySpinner() { |
| configuring_sync_ = true; |
| StringValue page("spinner"); |
| DictionaryValue args; |
| |
| const int kTimeoutSec = 30; |
| DCHECK(!backend_start_timer_.get()); |
| backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>()); |
| backend_start_timer_->Start(FROM_HERE, |
| base::TimeDelta::FromSeconds(kTimeoutSec), |
| this, &SyncSetupHandler::DisplayTimeout); |
| |
| web_ui()->CallJavascriptFunction( |
| "SyncSetupOverlay.showSyncSetupPage", page, args); |
| } |
| |
| // TODO(kochi): Handle error conditions other than timeout. |
| // http://crbug.com/128692 |
| void SyncSetupHandler::DisplayTimeout() { |
| // Stop a timer to handle timeout in waiting for checking network connection. |
| backend_start_timer_.reset(); |
| |
| // Do not listen to signin events. |
| signin_tracker_.reset(); |
| |
| StringValue page("timeout"); |
| DictionaryValue args; |
| web_ui()->CallJavascriptFunction( |
| "SyncSetupOverlay.showSyncSetupPage", page, args); |
| } |
| |
| void SyncSetupHandler::RecordSignin() { |
| // By default, do nothing - subclasses can override. |
| } |
| |
| void SyncSetupHandler::DisplayGaiaSuccessAndClose() { |
| RecordSignin(); |
| web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndClose"); |
| } |
| |
| void SyncSetupHandler::DisplayGaiaSuccessAndSettingUp() { |
| RecordSignin(); |
| web_ui()->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp"); |
| } |
| |
| void SyncSetupHandler::OnDidClosePage(const ListValue* args) { |
| CloseSyncSetup(); |
| } |
| |
| void SyncSetupHandler::HandleSubmitAuth(const ListValue* args) { |
| std::string json; |
| if (!args->GetString(0, &json)) { |
| NOTREACHED() << "Could not read JSON argument"; |
| return; |
| } |
| |
| if (json.empty()) |
| return; |
| |
| std::string username, password, captcha, otp, access_code; |
| if (!GetAuthData(json, &username, &password, &captcha, &otp, &access_code)) { |
| // The page sent us something that we didn't understand. |
| // This probably indicates a programming error. |
| NOTREACHED(); |
| return; |
| } |
| |
| string16 error_message; |
| if (!IsLoginAuthDataValid(username, &error_message)) { |
| DisplayGaiaLoginWithErrorMessage(error_message, false); |
| return; |
| } |
| |
| // If one of password, captcha, otp and access_code is non-empty, then the |
| // others must be empty. At least one should be non-empty. |
| DCHECK(password.empty() || |
| (captcha.empty() && otp.empty() && access_code.empty())); |
| DCHECK(captcha.empty() || |
| (password.empty() && otp.empty() && access_code.empty())); |
| DCHECK(otp.empty() || |
| (captcha.empty() && password.empty() && access_code.empty())); |
| DCHECK(access_code.empty() || |
| (captcha.empty() && password.empty() && otp.empty())); |
| DCHECK(!otp.empty() || !captcha.empty() || !password.empty() || |
| !access_code.empty()); |
| |
| if (IsClientOAuthEnabled()) { |
| // Last error is two-factor implies otp should not be empty. |
| DCHECK((last_signin_error_.state() != GoogleServiceAuthError::TWO_FACTOR) || |
| !otp.empty()); |
| // Last error is captcha-required implies captcha should not be empty. |
| DCHECK((last_signin_error_.state() != |
| GoogleServiceAuthError::CAPTCHA_REQUIRED) || !captcha.empty()); |
| } |
| |
| const std::string& solution = captcha.empty() ? |
| (otp.empty() ? EmptyString() : otp) : captcha; |
| TryLogin(username, password, solution, access_code); |
| } |
| |
| void SyncSetupHandler::TryLogin(const std::string& username, |
| const std::string& password, |
| const std::string& solution, |
| const std::string& access_code) { |
| DCHECK(IsActiveLogin()); |
| // Make sure we are listening for signin traffic. |
| if (!signin_tracker_.get()) |
| signin_tracker_.reset(new SigninTracker(GetProfile(), this)); |
| |
| last_attempted_user_email_ = username; |
| |
| // User is trying to log in again so reset the cached error. |
| GoogleServiceAuthError current_error = last_signin_error_; |
| last_signin_error_ = GoogleServiceAuthError::None(); |
| |
| SigninManager* signin = GetSignin(); |
| if (IsClientOAuthEnabled()) { |
| if (!solution.empty()) { |
| signin->ProvideOAuthChallengeResponse(current_error.state(), |
| current_error.token(), solution); |
| return; |
| } |
| } else { |
| // If we're just being called to provide an ASP, then pass it to the |
| // SigninManager and wait for the next step. |
| if (!access_code.empty()) { |
| signin->ProvideSecondFactorAccessCode(access_code); |
| return; |
| } |
| } |
| |
| // The user has submitted credentials, which indicates they don't want to |
| // suppress start up anymore. We do this before starting the signin process, |
| // so the ProfileSyncService knows to listen to the cached password. |
| GetSyncService()->UnsuppressAndStart(); |
| |
| // Kick off a sign-in through the signin manager. |
| if (IsClientOAuthEnabled()) { |
| signin->StartSignInWithOAuth(username, password); |
| } else { |
| signin->StartSignIn(username, password, current_error.captcha().token, |
| solution); |
| } |
| } |
| |
| void SyncSetupHandler::GaiaCredentialsValid() { |
| DCHECK(IsActiveLogin()); |
| |
| // Gaia credentials are valid - update the UI. |
| DisplayGaiaSuccessAndSettingUp(); |
| } |
| |
| void SyncSetupHandler::SigninFailed(const GoogleServiceAuthError& error) { |
| // Stop a timer to handle timeout in waiting for checking network connection. |
| backend_start_timer_.reset(); |
| |
| last_signin_error_ = error; |
| // Got a failed signin - this is either just a typical auth error, or a |
| // sync error (treat sync errors as "fatal errors" - i.e. non-auth errors). |
| // On ChromeOS, this condition can happen when auth token is invalid and |
| // cannot start sync backend. |
| if (retry_on_signin_failure_) { |
| DisplayGaiaLogin(GetSyncService()->HasUnrecoverableError()); |
| } else { |
| // TODO(peria): Show error dialog for prompting sign in and out on |
| // Chrome OS. http://crbug.com/128692 |
| CloseOverlay(); |
| } |
| } |
| |
| Profile* SyncSetupHandler::GetProfile() const { |
| return Profile::FromWebUI(web_ui()); |
| } |
| |
| ProfileSyncService* SyncSetupHandler::GetSyncService() const { |
| return ProfileSyncServiceFactory::GetForProfile(GetProfile()); |
| } |
| |
| void SyncSetupHandler::SigninSuccess() { |
| DCHECK(GetSyncService()->sync_initialized()); |
| // Stop a timer to handle timeout in waiting for checking network connection. |
| backend_start_timer_.reset(); |
| |
| // If we have signed in while sync is already setup, it must be due to some |
| // kind of re-authentication flow. In that case, just close the signin dialog |
| // rather than forcing the user to go through sync configuration. |
| if (GetSyncService()->HasSyncSetupCompleted()) |
| DisplayGaiaSuccessAndClose(); |
| else |
| DisplayConfigureSync(false, false); |
| } |
| |
| void SyncSetupHandler::HandleConfigure(const ListValue* args) { |
| std::string json; |
| if (!args->GetString(0, &json)) { |
| NOTREACHED() << "Could not read JSON argument"; |
| return; |
| } |
| if (json.empty()) { |
| NOTREACHED(); |
| return; |
| } |
| |
| SyncConfigInfo configuration; |
| if (!GetConfiguration(json, &configuration)) { |
| // The page sent us something that we didn't understand. |
| // This probably indicates a programming error. |
| NOTREACHED(); |
| return; |
| } |
| |
| // Start configuring the ProfileSyncService using the configuration passed |
| // to us from the JS layer. |
| ProfileSyncService* service = GetSyncService(); |
| |
| // If the sync engine has shutdown for some reason, just close the sync |
| // dialog. |
| if (!service->sync_initialized()) { |
| CloseOverlay(); |
| return; |
| } |
| |
| // Note: Data encryption will not occur until configuration is complete |
| // (when the PSS receives its CONFIGURE_DONE notification from the sync |
| // backend), so the user still has a chance to cancel out of the operation |
| // if (for example) some kind of passphrase error is encountered. |
| if (configuration.encrypt_all) |
| service->EnableEncryptEverything(); |
| |
| bool passphrase_failed = false; |
| if (!configuration.passphrase.empty()) { |
| // We call IsPassphraseRequired() here (instead of |
| // IsPassphraseRequiredForDecryption()) because the user may try to enter |
| // a passphrase even though no encrypted data types are enabled. |
| if (service->IsPassphraseRequired()) { |
| // If we have pending keys, try to decrypt them with the provided |
| // passphrase. We track if this succeeds or fails because a failed |
| // decryption should result in an error even if there aren't any encrypted |
| // data types. |
| passphrase_failed = |
| !service->SetDecryptionPassphrase(configuration.passphrase); |
| } else { |
| // OK, the user sent us a passphrase, but we don't have pending keys. So |
| // it either means that the pending keys were resolved somehow since the |
| // time the UI was displayed (re-encryption, pending passphrase change, |
| // etc) or the user wants to re-encrypt. |
| if (!configuration.passphrase_is_gaia && |
| !service->IsUsingSecondaryPassphrase()) { |
| // User passed us a secondary passphrase, and the data is encrypted |
| // with a GAIA passphrase so they must want to encrypt. |
| service->SetEncryptionPassphrase(configuration.passphrase, |
| ProfileSyncService::EXPLICIT); |
| } |
| } |
| } |
| |
| bool user_was_prompted_for_passphrase = |
| service->IsPassphraseRequiredForDecryption(); |
| service->OnUserChoseDatatypes(configuration.sync_everything, |
| configuration.data_types); |
| |
| // Need to call IsPassphraseRequiredForDecryption() *after* calling |
| // OnUserChoseDatatypes() because the user may have just disabled the |
| // encrypted datatypes (in which case we just want to exit, not prompt the |
| // user for a passphrase). |
| if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) { |
| // We need a passphrase, or the user's attempt to set a passphrase failed - |
| // prompt them again. This covers a few subtle cases: |
| // 1) The user enters an incorrect passphrase *and* disabled the encrypted |
| // data types. In that case we want to notify the user that the |
| // passphrase was incorrect even though there are no longer any encrypted |
| // types enabled (IsPassphraseRequiredForDecryption() == false). |
| // 2) The user doesn't enter any passphrase. In this case, we won't call |
| // SetDecryptionPassphrase() (passphrase_failed == false), but we still |
| // want to display an error message to let the user know that their |
| // blank passphrase entry is not acceptable. |
| // 3) The user just enabled an encrypted data type - in this case we don't |
| // want to display an "invalid passphrase" error, since it's the first |
| // time the user is seeing the prompt. |
| DisplayConfigureSync( |
| true, passphrase_failed || user_was_prompted_for_passphrase); |
| } else { |
| // No passphrase is required from the user so mark the configuration as |
| // complete and close the sync setup overlay. |
| ConfigureSyncDone(); |
| } |
| |
| ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE); |
| if (configuration.encrypt_all) |
| ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT); |
| if (configuration.passphrase_is_gaia && !configuration.passphrase.empty()) |
| ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE); |
| if (!configuration.sync_everything) |
| ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE); |
| } |
| |
| void SyncSetupHandler::HandleAttachHandler(const ListValue* args) { |
| bool force_login = false; |
| std::string json; |
| if (args->GetString(0, &json) && !json.empty()) { |
| scoped_ptr<Value> parsed_value(base::JSONReader::Read(json)); |
| DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); |
| result->GetBoolean("forceLogin", &force_login); |
| } |
| |
| OpenSyncSetup(force_login); |
| } |
| |
| void SyncSetupHandler::HandleShowErrorUI(const ListValue* args) { |
| DCHECK(!configuring_sync_); |
| |
| ProfileSyncService* service = GetSyncService(); |
| DCHECK(service); |
| |
| // Bring up the existing wizard, or just display it on this page. |
| if (!FocusExistingWizardIfPresent()) |
| OpenSyncSetup(false); |
| } |
| |
| void SyncSetupHandler::HandleShowSetupUI(const ListValue* args) { |
| OpenSyncSetup(false); |
| } |
| |
| void SyncSetupHandler::HandleShowSetupUIWithoutLogin(const ListValue* args) { |
| OpenConfigureSync(); |
| } |
| |
| void SyncSetupHandler::HandleDoSignOutOnAuthError(const ListValue* args) { |
| DLOG(INFO) << "Signing out the user to fix a sync error."; |
| browser::AttemptUserExit(); |
| } |
| |
| void SyncSetupHandler::HandleStopSyncing(const ListValue* args) { |
| ProfileSyncService* service = GetSyncService(); |
| DCHECK(service); |
| |
| if (ProfileSyncService::IsSyncEnabled()) { |
| service->DisableForUser(); |
| ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS); |
| } |
| } |
| |
| void SyncSetupHandler::HandleCloseTimeout(const ListValue* args) { |
| CloseSyncSetup(); |
| } |
| |
| void SyncSetupHandler::CloseSyncSetup() { |
| // TODO(atwilson): Move UMA tracking of signin events out of sync module. |
| ProfileSyncService* sync_service = GetSyncService(); |
| if (IsActiveLogin()) { |
| if (!sync_service->HasSyncSetupCompleted()) { |
| if (signin_tracker_.get()) { |
| ProfileSyncService::SyncEvent( |
| ProfileSyncService::CANCEL_DURING_SIGNON); |
| } else if (configuring_sync_) { |
| ProfileSyncService::SyncEvent( |
| ProfileSyncService::CANCEL_DURING_CONFIGURE); |
| } else { |
| ProfileSyncService::SyncEvent( |
| ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH); |
| } |
| } |
| // Let the various services know that we're no longer active. |
| GetLoginUIService()->LoginUIClosed(this); |
| } |
| |
| if (sync_service) { |
| sync_service->SetSetupInProgress(false); |
| |
| // Make sure user isn't left half-logged-in (signed in, but without sync |
| // started up). If the user hasn't finished setting up sync, then sign out |
| // and shut down sync. |
| if (!sync_service->HasSyncSetupCompleted()) { |
| DVLOG(1) << "Signin aborted by user action"; |
| sync_service->DisableForUser(); |
| |
| browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs()); |
| sync_prefs.SetStartSuppressed(true); |
| } |
| } |
| |
| // Reset the attempted email address and error, otherwise the sync setup |
| // overlay in the settings page will stay in whatever error state it was last |
| // when it is reopened. |
| last_attempted_user_email_.clear(); |
| last_signin_error_ = GoogleServiceAuthError::None(); |
| |
| configuring_sync_ = false; |
| signin_tracker_.reset(); |
| |
| // Stop a timer to handle timeout in waiting for checking network connection. |
| backend_start_timer_.reset(); |
| } |
| |
| void SyncSetupHandler::OpenSyncSetup(bool force_login) { |
| if (!PrepareSyncSetup()) |
| return; |
| |
| ProfileSyncService* service = GetSyncService(); |
| |
| // There are several different UI flows that can bring the user here: |
| // 1) Signin promo (passes force_login=true) |
| // 2) Normal signin through options page (IsSyncEnabledAndLoggedIn() will |
| // return false). |
| // 3) Previously working credentials have expired |
| // (service->GetAuthError() != NONE). |
| // 4) User is already signed in, but App Notifications needs to force another |
| // login so it can fetch an oauth token (passes force_login=true) |
| // 5) User clicks [Advanced Settings] button on options page while already |
| // logged in. |
| // 6) One-click signin (credentials are already available, so should display |
| // sync configure UI, not login UI). |
| if (force_login || |
| !service->IsSyncEnabledAndLoggedIn() || |
| service->GetAuthError().state() != GoogleServiceAuthError::NONE) { |
| // User is not logged in, or login has been specially requested - need to |
| // display login UI (cases 1-4). |
| DisplayGaiaLogin(false); |
| } else { |
| // User is already logged in. They must have brought up the config wizard |
| // via the "Advanced..." button or through One-Click signin (cases 5/6). |
| DisplayConfigureSync(true, false); |
| } |
| |
| ShowSetupUI(); |
| } |
| |
| void SyncSetupHandler::OpenConfigureSync() { |
| if (!PrepareSyncSetup()) |
| return; |
| |
| DisplayConfigureSync(true, false); |
| ShowSetupUI(); |
| } |
| |
| void SyncSetupHandler::FocusUI() { |
| DCHECK(IsActiveLogin()); |
| WebContents* web_contents = web_ui()->GetWebContents(); |
| web_contents->GetDelegate()->ActivateContents(web_contents); |
| } |
| |
| void SyncSetupHandler::CloseUI() { |
| DCHECK(IsActiveLogin()); |
| CloseOverlay(); |
| } |
| |
| // Private member functions. |
| |
| bool SyncSetupHandler::FocusExistingWizardIfPresent() { |
| LoginUIService* service = GetLoginUIService(); |
| if (!service->current_login_ui()) |
| return false; |
| service->current_login_ui()->FocusUI(); |
| return true; |
| } |
| |
| LoginUIService* SyncSetupHandler::GetLoginUIService() const { |
| return LoginUIServiceFactory::GetForProfile(GetProfile()); |
| } |
| |
| void SyncSetupHandler::CloseOverlay() { |
| // Stop a timer to handle timeout in waiting for sync setup. |
| backend_start_timer_.reset(); |
| |
| CloseSyncSetup(); |
| web_ui()->CallJavascriptFunction("OptionsPage.closeOverlay"); |
| } |
| |
| bool SyncSetupHandler::IsLoginAuthDataValid(const std::string& username, |
| string16* error_message) { |
| if (username.empty()) |
| return true; |
| |
| // Can be null during some unit tests. |
| if (!web_ui()) |
| return true; |
| |
| if (!GetSignin()->IsAllowedUsername(username)) { |
| *error_message = l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_NAME_PROHIBITED); |
| return false; |
| } |
| |
| // If running in a unit test, skip profile check. |
| if (!profile_manager_) |
| return true; |
| |
| // Check if the username is already in use by another profile. |
| const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); |
| size_t current_profile_index = |
| cache.GetIndexOfProfileWithPath(GetProfile()->GetPath()); |
| string16 username_utf16 = UTF8ToUTF16(username); |
| |
| for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { |
| if (i != current_profile_index && AreUserNamesEqual( |
| cache.GetUserNameOfProfileAtIndex(i), username_utf16)) { |
| *error_message = l10n_util::GetStringUTF16( |
| IDS_SYNC_USER_NAME_IN_USE_ERROR); |
| return false; |
| } |
| } |
| |
| return true; |
| } |