1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/webui/sync_setup_handler.h" 6 7 #include "base/basictypes.h" 8 #include "base/bind.h" 9 #include "base/bind_helpers.h" 10 #include "base/command_line.h" 11 #include "base/compiler_specific.h" 12 #include "base/i18n/time_formatting.h" 13 #include "base/json/json_reader.h" 14 #include "base/json/json_writer.h" 15 #include "base/metrics/histogram.h" 16 #include "base/prefs/pref_service.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/values.h" 19 #include "chrome/app/chrome_command_ids.h" 20 #include "chrome/browser/browser_process.h" 21 #include "chrome/browser/lifetime/application_lifetime.h" 22 #include "chrome/browser/profiles/profile.h" 23 #include "chrome/browser/profiles/profile_info_cache.h" 24 #include "chrome/browser/profiles/profile_manager.h" 25 #include "chrome/browser/profiles/profile_metrics.h" 26 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 27 #include "chrome/browser/signin/signin_header_helper.h" 28 #include "chrome/browser/signin/signin_manager_factory.h" 29 #include "chrome/browser/signin/signin_promo.h" 30 #include "chrome/browser/sync/profile_sync_service.h" 31 #include "chrome/browser/sync/profile_sync_service_factory.h" 32 #include "chrome/browser/ui/browser_finder.h" 33 #include "chrome/browser/ui/browser_navigator.h" 34 #include "chrome/browser/ui/browser_window.h" 35 #include "chrome/browser/ui/singleton_tabs.h" 36 #include "chrome/browser/ui/sync/signin_histogram.h" 37 #include "chrome/browser/ui/webui/options/options_handlers_helper.h" 38 #include "chrome/browser/ui/webui/signin/login_ui_service.h" 39 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 40 #include "chrome/common/chrome_switches.h" 41 #include "chrome/common/pref_names.h" 42 #include "chrome/common/url_constants.h" 43 #include "chrome/grit/chromium_strings.h" 44 #include "chrome/grit/generated_resources.h" 45 #include "chrome/grit/locale_settings.h" 46 #include "components/google/core/browser/google_util.h" 47 #include "components/signin/core/browser/profile_oauth2_token_service.h" 48 #include "components/signin/core/browser/signin_error_controller.h" 49 #include "components/signin/core/browser/signin_metrics.h" 50 #include "components/signin/core/common/profile_management_switches.h" 51 #include "components/sync_driver/sync_prefs.h" 52 #include "content/public/browser/render_view_host.h" 53 #include "content/public/browser/web_contents.h" 54 #include "content/public/browser/web_contents_delegate.h" 55 #include "google_apis/gaia/gaia_auth_util.h" 56 #include "google_apis/gaia/gaia_constants.h" 57 #include "grit/components_strings.h" 58 #include "net/base/url_util.h" 59 #include "ui/base/l10n/l10n_util.h" 60 61 #if defined(OS_CHROMEOS) 62 #include "components/signin/core/browser/signin_manager_base.h" 63 #else 64 #include "components/signin/core/browser/signin_manager.h" 65 #endif 66 67 using content::WebContents; 68 using l10n_util::GetStringFUTF16; 69 using l10n_util::GetStringUTF16; 70 71 namespace { 72 73 // A structure which contains all the configuration information for sync. 74 struct SyncConfigInfo { 75 SyncConfigInfo(); 76 ~SyncConfigInfo(); 77 78 bool encrypt_all; 79 bool sync_everything; 80 bool sync_nothing; 81 syncer::ModelTypeSet data_types; 82 std::string passphrase; 83 bool passphrase_is_gaia; 84 }; 85 86 SyncConfigInfo::SyncConfigInfo() 87 : encrypt_all(false), 88 sync_everything(false), 89 sync_nothing(false), 90 passphrase_is_gaia(false) { 91 } 92 93 SyncConfigInfo::~SyncConfigInfo() {} 94 95 // Note: The order of these types must match the ordering of 96 // the respective types in ModelType 97 const char* kDataTypeNames[] = { 98 "bookmarks", 99 "preferences", 100 "passwords", 101 "autofill", 102 "themes", 103 "typedUrls", 104 "extensions", 105 "apps", 106 "tabs" 107 }; 108 109 COMPILE_ASSERT(32 == syncer::MODEL_TYPE_COUNT, 110 update_kDataTypeNames_to_match_UserSelectableTypes); 111 112 typedef std::map<syncer::ModelType, const char*> ModelTypeNameMap; 113 114 ModelTypeNameMap GetSelectableTypeNameMap() { 115 ModelTypeNameMap type_names; 116 syncer::ModelTypeSet type_set = syncer::UserSelectableTypes(); 117 syncer::ModelTypeSet::Iterator it = type_set.First(); 118 DCHECK_EQ(arraysize(kDataTypeNames), type_set.Size()); 119 for (size_t i = 0; i < arraysize(kDataTypeNames) && it.Good(); 120 ++i, it.Inc()) { 121 type_names[it.Get()] = kDataTypeNames[i]; 122 } 123 return type_names; 124 } 125 126 bool GetConfiguration(const std::string& json, SyncConfigInfo* config) { 127 scoped_ptr<base::Value> parsed_value(base::JSONReader::Read(json)); 128 base::DictionaryValue* result; 129 if (!parsed_value || !parsed_value->GetAsDictionary(&result)) { 130 DLOG(ERROR) << "GetConfiguration() not passed a Dictionary"; 131 return false; 132 } 133 134 if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) { 135 DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value"; 136 return false; 137 } 138 139 if (!result->GetBoolean("syncNothing", &config->sync_nothing)) { 140 DLOG(ERROR) << "GetConfiguration() not passed a syncNothing value"; 141 return false; 142 } 143 144 DCHECK(!(config->sync_everything && config->sync_nothing)) 145 << "syncAllDataTypes and syncNothing cannot both be true"; 146 147 ModelTypeNameMap type_names = GetSelectableTypeNameMap(); 148 149 for (ModelTypeNameMap::const_iterator it = type_names.begin(); 150 it != type_names.end(); ++it) { 151 std::string key_name = it->second + std::string("Synced"); 152 bool sync_value; 153 if (!result->GetBoolean(key_name, &sync_value)) { 154 DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name; 155 return false; 156 } 157 if (sync_value) 158 config->data_types.Put(it->first); 159 } 160 161 // Encryption settings. 162 if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) { 163 DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData"; 164 return false; 165 } 166 167 // Passphrase settings. 168 bool have_passphrase; 169 if (!result->GetBoolean("usePassphrase", &have_passphrase)) { 170 DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value"; 171 return false; 172 } 173 174 if (have_passphrase) { 175 if (!result->GetBoolean("isGooglePassphrase", 176 &config->passphrase_is_gaia)) { 177 DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value"; 178 return false; 179 } 180 if (!result->GetString("passphrase", &config->passphrase)) { 181 DLOG(ERROR) << "GetConfiguration() not passed a passphrase value"; 182 return false; 183 } 184 } 185 return true; 186 } 187 188 } // namespace 189 190 SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager) 191 : configuring_sync_(false), 192 profile_manager_(profile_manager) { 193 } 194 195 SyncSetupHandler::~SyncSetupHandler() { 196 // Just exit if running unit tests (no actual WebUI is attached). 197 if (!web_ui()) 198 return; 199 200 // This case is hit when the user performs a back navigation. 201 CloseSyncSetup(); 202 } 203 204 void SyncSetupHandler::GetLocalizedValues( 205 base::DictionaryValue* localized_strings) { 206 GetStaticLocalizedValues(localized_strings, web_ui()); 207 } 208 209 void SyncSetupHandler::GetStaticLocalizedValues( 210 base::DictionaryValue* localized_strings, 211 content::WebUI* web_ui) { 212 DCHECK(localized_strings); 213 214 base::string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME)); 215 localized_strings->SetString( 216 "chooseDataTypesInstructions", 217 GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name)); 218 localized_strings->SetString( 219 "encryptionInstructions", 220 GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name)); 221 localized_strings->SetString( 222 "encryptionHelpURL", chrome::kSyncEncryptionHelpURL); 223 localized_strings->SetString( 224 "encryptionSectionMessage", 225 GetStringFUTF16(IDS_SYNC_ENCRYPTION_SECTION_MESSAGE, product_name)); 226 localized_strings->SetString( 227 "passphraseRecover", 228 GetStringFUTF16( 229 IDS_SYNC_PASSPHRASE_RECOVER, 230 base::ASCIIToUTF16( 231 google_util::AppendGoogleLocaleParam( 232 GURL(chrome::kSyncGoogleDashboardURL), 233 g_browser_process->GetApplicationLocale()).spec()))); 234 localized_strings->SetString( 235 "stopSyncingExplanation", 236 l10n_util::GetStringFUTF16( 237 IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL, 238 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), 239 base::ASCIIToUTF16( 240 google_util::AppendGoogleLocaleParam( 241 GURL(chrome::kSyncGoogleDashboardURL), 242 g_browser_process->GetApplicationLocale()).spec()))); 243 localized_strings->SetString("deleteProfileLabel", 244 l10n_util::GetStringUTF16(IDS_SYNC_STOP_DELETE_PROFILE_LABEL)); 245 localized_strings->SetString("stopSyncingTitle", 246 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE)); 247 localized_strings->SetString("stopSyncingConfirm", 248 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL)); 249 250 localized_strings->SetString( 251 "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL); 252 localized_strings->SetString( 253 "syncErrorHelpURL", chrome::kSyncErrorsHelpURL); 254 255 static OptionsStringResource resources[] = { 256 { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE }, 257 { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE }, 258 { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE }, 259 { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT }, 260 { "errorLearnMore", IDS_LEARN_MORE }, 261 { "cancel", IDS_CANCEL }, 262 { "loginSuccess", IDS_SYNC_SUCCESS }, 263 { "settingUp", IDS_SYNC_LOGIN_SETTING_UP }, 264 { "syncAllDataTypes", IDS_SYNC_EVERYTHING }, 265 { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES }, 266 { "syncNothing", IDS_SYNC_NOTHING }, 267 { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS }, 268 { "preferences", IDS_SYNC_DATATYPE_PREFERENCES }, 269 { "autofill", IDS_SYNC_DATATYPE_AUTOFILL }, 270 { "themes", IDS_SYNC_DATATYPE_THEMES }, 271 { "passwords", IDS_SYNC_DATATYPE_PASSWORDS }, 272 { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS }, 273 { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS }, 274 { "apps", IDS_SYNC_DATATYPE_APPS }, 275 { "openTabs", IDS_SYNC_DATATYPE_TABS }, 276 { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR }, 277 { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL }, 278 { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR }, 279 { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR }, 280 { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL }, 281 { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES }, 282 { "syncEverything", IDS_SYNC_SYNC_EVERYTHING }, 283 { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS }, 284 { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY }, 285 { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY }, 286 { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL }, 287 { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE }, 288 { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING }, 289 { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES }, 290 { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO }, 291 { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX }, 292 { "sectionExplicitMessagePostfix", 293 IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX }, 294 // TODO(rogerta): browser/resource/sync_promo/sync_promo.html and related 295 // file may not be needed any more. If not, then the following promo 296 // strings can also be removed. 297 { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE }, 298 { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON }, 299 { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED }, 300 { "promoLearnMore", IDS_LEARN_MORE }, 301 { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT }, 302 { "encryptionSectionTitle", IDS_SYNC_ENCRYPTION_SECTION_TITLE }, 303 { "basicEncryptionOption", IDS_SYNC_BASIC_ENCRYPTION_DATA }, 304 { "fullEncryptionOption", IDS_SYNC_FULL_ENCRYPTION_DATA }, 305 }; 306 307 RegisterStrings(localized_strings, resources, arraysize(resources)); 308 RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE); 309 } 310 311 void SyncSetupHandler::DisplayConfigureSync(bool show_advanced, 312 bool passphrase_failed) { 313 // Should never call this when we are not signed in. 314 DCHECK(SigninManagerFactory::GetForProfile( 315 GetProfile())->IsAuthenticated()); 316 ProfileSyncService* service = GetSyncService(); 317 DCHECK(service); 318 if (!service->sync_initialized()) { 319 service->UnsuppressAndStart(); 320 321 // See if it's even possible to bring up the sync backend - if not 322 // (unrecoverable error?), don't bother displaying a spinner that will be 323 // immediately closed because this leads to some ugly infinite UI loop (see 324 // http://crbug.com/244769). 325 if (SyncStartupTracker::GetSyncServiceState(GetProfile()) != 326 SyncStartupTracker::SYNC_STARTUP_ERROR) { 327 DisplaySpinner(); 328 } 329 330 // Start SyncSetupTracker to wait for sync to initialize. 331 sync_startup_tracker_.reset( 332 new SyncStartupTracker(GetProfile(), this)); 333 return; 334 } 335 336 // Should only get here if user is signed in and sync is initialized, so no 337 // longer need a SyncStartupTracker. 338 sync_startup_tracker_.reset(); 339 configuring_sync_ = true; 340 DCHECK(service->sync_initialized()) << 341 "Cannot configure sync until the sync backend is initialized"; 342 343 // Setup args for the sync configure screen: 344 // showSyncEverythingPage: false to skip directly to the configure screen 345 // syncAllDataTypes: true if the user wants to sync everything 346 // syncNothing: true if the user wants to sync nothing 347 // <data_type>Registered: true if the associated data type is supported 348 // <data_type>Synced: true if the user wants to sync that specific data type 349 // encryptionEnabled: true if sync supports encryption 350 // encryptAllData: true if user wants to encrypt all data (not just 351 // passwords) 352 // usePassphrase: true if the data is encrypted with a secondary passphrase 353 // show_passphrase: true if a passphrase is needed to decrypt the sync data 354 base::DictionaryValue args; 355 356 // Tell the UI layer which data types are registered/enabled by the user. 357 const syncer::ModelTypeSet registered_types = 358 service->GetRegisteredDataTypes(); 359 const syncer::ModelTypeSet preferred_types = service->GetPreferredDataTypes(); 360 const syncer::ModelTypeSet enforced_types = service->GetForcedDataTypes(); 361 ModelTypeNameMap type_names = GetSelectableTypeNameMap(); 362 for (ModelTypeNameMap::const_iterator it = type_names.begin(); 363 it != type_names.end(); ++it) { 364 syncer::ModelType sync_type = it->first; 365 const std::string key_name = it->second; 366 args.SetBoolean(key_name + "Registered", registered_types.Has(sync_type)); 367 args.SetBoolean(key_name + "Synced", preferred_types.Has(sync_type)); 368 args.SetBoolean(key_name + "Enforced", enforced_types.Has(sync_type)); 369 // TODO(treib): How do we want to handle pref groups, i.e. when only some of 370 // the sync types behind a checkbox are force-enabled? crbug.com/403326 371 } 372 sync_driver::SyncPrefs sync_prefs(GetProfile()->GetPrefs()); 373 args.SetBoolean("passphraseFailed", passphrase_failed); 374 args.SetBoolean("showSyncEverythingPage", !show_advanced); 375 args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced()); 376 args.SetBoolean("syncNothing", false); // Always false during initial setup. 377 args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled()); 378 args.SetBoolean("encryptAllDataAllowed", service->EncryptEverythingAllowed()); 379 380 // We call IsPassphraseRequired() here, instead of calling 381 // IsPassphraseRequiredForDecryption(), because we want to show the passphrase 382 // UI even if no encrypted data types are enabled. 383 args.SetBoolean("showPassphrase", service->IsPassphraseRequired()); 384 385 // To distinguish between FROZEN_IMPLICIT_PASSPHRASE and CUSTOM_PASSPHRASE 386 // we only set usePassphrase for CUSTOM_PASSPHRASE. 387 args.SetBoolean("usePassphrase", 388 service->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE); 389 base::Time passphrase_time = service->GetExplicitPassphraseTime(); 390 syncer::PassphraseType passphrase_type = service->GetPassphraseType(); 391 if (!passphrase_time.is_null()) { 392 base::string16 passphrase_time_str = 393 base::TimeFormatShortDate(passphrase_time); 394 args.SetString( 395 "enterPassphraseBody", 396 GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE, 397 passphrase_time_str)); 398 args.SetString( 399 "enterGooglePassphraseBody", 400 GetStringFUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE, 401 passphrase_time_str)); 402 switch (passphrase_type) { 403 case syncer::FROZEN_IMPLICIT_PASSPHRASE: 404 args.SetString( 405 "fullEncryptionBody", 406 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_GOOGLE_WITH_DATE, 407 passphrase_time_str)); 408 break; 409 case syncer::CUSTOM_PASSPHRASE: 410 args.SetString( 411 "fullEncryptionBody", 412 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM_WITH_DATE, 413 passphrase_time_str)); 414 break; 415 default: 416 args.SetString( 417 "fullEncryptionBody", 418 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM)); 419 break; 420 } 421 } else if (passphrase_type == syncer::CUSTOM_PASSPHRASE) { 422 args.SetString( 423 "fullEncryptionBody", 424 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM)); 425 } else { 426 args.SetString( 427 "fullEncryptionBody", 428 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_DATA)); 429 } 430 431 base::StringValue page("configure"); 432 web_ui()->CallJavascriptFunction( 433 "SyncSetupOverlay.showSyncSetupPage", page, args); 434 435 // Make sure the tab used for the Gaia sign in does not cover the settings 436 // tab. 437 FocusUI(); 438 } 439 440 void SyncSetupHandler::ConfigureSyncDone() { 441 base::StringValue page("done"); 442 web_ui()->CallJavascriptFunction( 443 "SyncSetupOverlay.showSyncSetupPage", page); 444 445 // Suppress the sign in promo once the user starts sync. This way the user 446 // doesn't see the sign in promo even if they sign out later on. 447 signin::SetUserSkippedPromo(GetProfile()); 448 449 ProfileSyncService* service = GetSyncService(); 450 DCHECK(service); 451 if (!service->HasSyncSetupCompleted()) { 452 // This is the first time configuring sync, so log it. 453 base::FilePath profile_file_path = GetProfile()->GetPath(); 454 ProfileMetrics::LogProfileSyncSignIn(profile_file_path); 455 456 // We're done configuring, so notify ProfileSyncService that it is OK to 457 // start syncing. 458 service->SetSetupInProgress(false); 459 service->SetSyncSetupCompleted(); 460 } 461 } 462 463 bool SyncSetupHandler::IsActiveLogin() const { 464 // LoginUIService can be NULL if page is brought up in incognito mode 465 // (i.e. if the user is running in guest mode in cros and brings up settings). 466 LoginUIService* service = GetLoginUIService(); 467 return service && (service->current_login_ui() == this); 468 } 469 470 void SyncSetupHandler::RegisterMessages() { 471 web_ui()->RegisterMessageCallback( 472 "SyncSetupDidClosePage", 473 base::Bind(&SyncSetupHandler::OnDidClosePage, 474 base::Unretained(this))); 475 web_ui()->RegisterMessageCallback( 476 "SyncSetupConfigure", 477 base::Bind(&SyncSetupHandler::HandleConfigure, 478 base::Unretained(this))); 479 web_ui()->RegisterMessageCallback( 480 "SyncSetupShowSetupUI", 481 base::Bind(&SyncSetupHandler::HandleShowSetupUI, 482 base::Unretained(this))); 483 web_ui()->RegisterMessageCallback("CloseTimeout", 484 base::Bind(&SyncSetupHandler::HandleCloseTimeout, 485 base::Unretained(this))); 486 #if defined(OS_CHROMEOS) 487 web_ui()->RegisterMessageCallback( 488 "SyncSetupDoSignOutOnAuthError", 489 base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError, 490 base::Unretained(this))); 491 #else 492 web_ui()->RegisterMessageCallback("SyncSetupStopSyncing", 493 base::Bind(&SyncSetupHandler::HandleStopSyncing, 494 base::Unretained(this))); 495 web_ui()->RegisterMessageCallback("SyncSetupStartSignIn", 496 base::Bind(&SyncSetupHandler::HandleStartSignin, 497 base::Unretained(this))); 498 #endif 499 } 500 501 #if !defined(OS_CHROMEOS) 502 void SyncSetupHandler::DisplayGaiaLogin() { 503 DCHECK(!sync_startup_tracker_); 504 // Advanced options are no longer being configured if the login screen is 505 // visible. If the user exits the signin wizard after this without 506 // configuring sync, CloseSyncSetup() will ensure they are logged out. 507 configuring_sync_ = false; 508 DisplayGaiaLoginInNewTabOrWindow(); 509 } 510 511 void SyncSetupHandler::DisplayGaiaLoginInNewTabOrWindow() { 512 Browser* browser = chrome::FindBrowserWithWebContents( 513 web_ui()->GetWebContents()); 514 bool force_new_tab = false; 515 if (!browser) { 516 // Settings is not displayed in a browser window. Open a new window. 517 browser = new Browser(Browser::CreateParams( 518 Browser::TYPE_TABBED, GetProfile(), chrome::GetActiveDesktop())); 519 force_new_tab = true; 520 } 521 522 // If the signin manager already has an authenticated username, this is a 523 // re-auth scenario, and we need to ensure that the user signs in with the 524 // same email address. 525 GURL url; 526 if (SigninManagerFactory::GetForProfile( 527 browser->profile())->IsAuthenticated()) { 528 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth", 529 signin::HISTOGRAM_SHOWN, 530 signin::HISTOGRAM_MAX); 531 532 SigninErrorController* error_controller = 533 ProfileOAuth2TokenServiceFactory::GetForProfile(browser->profile())-> 534 signin_error_controller(); 535 DCHECK(error_controller->HasError()); 536 if (switches::IsNewAvatarMenu() && !force_new_tab) { 537 browser->window()->ShowAvatarBubbleFromAvatarButton( 538 BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH, 539 signin::ManageAccountsParams()); 540 } else { 541 url = signin::GetReauthURL(browser->profile(), 542 error_controller->error_account_id()); 543 } 544 } else { 545 if (switches::IsNewAvatarMenu() && !force_new_tab) { 546 browser->window()->ShowAvatarBubbleFromAvatarButton( 547 BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN, 548 signin::ManageAccountsParams()); 549 } else { 550 url = signin::GetPromoURL(signin::SOURCE_SETTINGS, true); 551 } 552 } 553 554 if (url.is_valid()) 555 chrome::ShowSingletonTab(browser, url); 556 } 557 #endif 558 559 bool SyncSetupHandler::PrepareSyncSetup() { 560 // If the wizard is already visible, just focus that one. 561 if (FocusExistingWizardIfPresent()) { 562 if (!IsActiveLogin()) 563 CloseSyncSetup(); 564 return false; 565 } 566 567 // Notify services that login UI is now active. 568 GetLoginUIService()->SetLoginUI(this); 569 570 ProfileSyncService* service = GetSyncService(); 571 if (service) 572 service->SetSetupInProgress(true); 573 574 return true; 575 } 576 577 void SyncSetupHandler::DisplaySpinner() { 578 configuring_sync_ = true; 579 base::StringValue page("spinner"); 580 base::DictionaryValue args; 581 582 const int kTimeoutSec = 30; 583 DCHECK(!backend_start_timer_); 584 backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>()); 585 backend_start_timer_->Start(FROM_HERE, 586 base::TimeDelta::FromSeconds(kTimeoutSec), 587 this, &SyncSetupHandler::DisplayTimeout); 588 589 web_ui()->CallJavascriptFunction( 590 "SyncSetupOverlay.showSyncSetupPage", page, args); 591 } 592 593 // TODO(kochi): Handle error conditions other than timeout. 594 // http://crbug.com/128692 595 void SyncSetupHandler::DisplayTimeout() { 596 // Stop a timer to handle timeout in waiting for checking network connection. 597 backend_start_timer_.reset(); 598 599 // Do not listen to sync startup events. 600 sync_startup_tracker_.reset(); 601 602 base::StringValue page("timeout"); 603 base::DictionaryValue args; 604 web_ui()->CallJavascriptFunction( 605 "SyncSetupOverlay.showSyncSetupPage", page, args); 606 } 607 608 void SyncSetupHandler::OnDidClosePage(const base::ListValue* args) { 609 CloseSyncSetup(); 610 } 611 612 void SyncSetupHandler::SyncStartupFailed() { 613 // Stop a timer to handle timeout in waiting for checking network connection. 614 backend_start_timer_.reset(); 615 616 // Just close the sync overlay (the idea is that the base settings page will 617 // display the current error.) 618 CloseUI(); 619 } 620 621 void SyncSetupHandler::SyncStartupCompleted() { 622 ProfileSyncService* service = GetSyncService(); 623 DCHECK(service->sync_initialized()); 624 625 // Stop a timer to handle timeout in waiting for checking network connection. 626 backend_start_timer_.reset(); 627 628 DisplayConfigureSync(true, false); 629 } 630 631 Profile* SyncSetupHandler::GetProfile() const { 632 return Profile::FromWebUI(web_ui()); 633 } 634 635 ProfileSyncService* SyncSetupHandler::GetSyncService() const { 636 Profile* profile = GetProfile(); 637 return profile->IsSyncAccessible() ? 638 ProfileSyncServiceFactory::GetForProfile(GetProfile()) : NULL; 639 } 640 641 void SyncSetupHandler::HandleConfigure(const base::ListValue* args) { 642 DCHECK(!sync_startup_tracker_); 643 std::string json; 644 if (!args->GetString(0, &json)) { 645 NOTREACHED() << "Could not read JSON argument"; 646 return; 647 } 648 if (json.empty()) { 649 NOTREACHED(); 650 return; 651 } 652 653 SyncConfigInfo configuration; 654 if (!GetConfiguration(json, &configuration)) { 655 // The page sent us something that we didn't understand. 656 // This probably indicates a programming error. 657 NOTREACHED(); 658 return; 659 } 660 661 // Start configuring the ProfileSyncService using the configuration passed 662 // to us from the JS layer. 663 ProfileSyncService* service = GetSyncService(); 664 665 // If the sync engine has shutdown for some reason, just close the sync 666 // dialog. 667 if (!service || !service->sync_initialized()) { 668 CloseUI(); 669 return; 670 } 671 672 // Disable sync, but remain signed in if the user selected "Sync nothing" in 673 // the advanced settings dialog. Note: In order to disable sync across 674 // restarts on Chrome OS, we must call StopSyncingPermanently(), which 675 // suppresses sync startup in addition to disabling it. 676 if (configuration.sync_nothing) { 677 ProfileSyncService::SyncEvent( 678 ProfileSyncService::STOP_FROM_ADVANCED_DIALOG); 679 CloseUI(); 680 service->StopSyncingPermanently(); 681 service->SetSetupInProgress(false); 682 return; 683 } 684 685 // Don't allow "encrypt all" if the ProfileSyncService doesn't allow it. 686 // The UI is hidden, but the user may have enabled it e.g. by fiddling with 687 // the web inspector. 688 if (!service->EncryptEverythingAllowed()) 689 configuration.encrypt_all = false; 690 691 // Note: Data encryption will not occur until configuration is complete 692 // (when the PSS receives its CONFIGURE_DONE notification from the sync 693 // backend), so the user still has a chance to cancel out of the operation 694 // if (for example) some kind of passphrase error is encountered. 695 if (configuration.encrypt_all) 696 service->EnableEncryptEverything(); 697 698 bool passphrase_failed = false; 699 if (!configuration.passphrase.empty()) { 700 // We call IsPassphraseRequired() here (instead of 701 // IsPassphraseRequiredForDecryption()) because the user may try to enter 702 // a passphrase even though no encrypted data types are enabled. 703 if (service->IsPassphraseRequired()) { 704 // If we have pending keys, try to decrypt them with the provided 705 // passphrase. We track if this succeeds or fails because a failed 706 // decryption should result in an error even if there aren't any encrypted 707 // data types. 708 passphrase_failed = 709 !service->SetDecryptionPassphrase(configuration.passphrase); 710 } else { 711 // OK, the user sent us a passphrase, but we don't have pending keys. So 712 // it either means that the pending keys were resolved somehow since the 713 // time the UI was displayed (re-encryption, pending passphrase change, 714 // etc) or the user wants to re-encrypt. 715 if (!configuration.passphrase_is_gaia && 716 !service->IsUsingSecondaryPassphrase()) { 717 // User passed us a secondary passphrase, and the data is encrypted 718 // with a GAIA passphrase so they must want to encrypt. 719 service->SetEncryptionPassphrase(configuration.passphrase, 720 ProfileSyncService::EXPLICIT); 721 } 722 } 723 } 724 725 bool user_was_prompted_for_passphrase = 726 service->IsPassphraseRequiredForDecryption(); 727 service->OnUserChoseDatatypes(configuration.sync_everything, 728 configuration.data_types); 729 730 // Need to call IsPassphraseRequiredForDecryption() *after* calling 731 // OnUserChoseDatatypes() because the user may have just disabled the 732 // encrypted datatypes (in which case we just want to exit, not prompt the 733 // user for a passphrase). 734 if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) { 735 // We need a passphrase, or the user's attempt to set a passphrase failed - 736 // prompt them again. This covers a few subtle cases: 737 // 1) The user enters an incorrect passphrase *and* disabled the encrypted 738 // data types. In that case we want to notify the user that the 739 // passphrase was incorrect even though there are no longer any encrypted 740 // types enabled (IsPassphraseRequiredForDecryption() == false). 741 // 2) The user doesn't enter any passphrase. In this case, we won't call 742 // SetDecryptionPassphrase() (passphrase_failed == false), but we still 743 // want to display an error message to let the user know that their 744 // blank passphrase entry is not acceptable. 745 // 3) The user just enabled an encrypted data type - in this case we don't 746 // want to display an "invalid passphrase" error, since it's the first 747 // time the user is seeing the prompt. 748 DisplayConfigureSync( 749 true, passphrase_failed || user_was_prompted_for_passphrase); 750 } else { 751 // No passphrase is required from the user so mark the configuration as 752 // complete and close the sync setup overlay. 753 ConfigureSyncDone(); 754 } 755 756 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE); 757 if (configuration.encrypt_all) 758 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT); 759 if (configuration.passphrase_is_gaia && !configuration.passphrase.empty()) 760 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE); 761 if (!configuration.sync_everything) 762 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE); 763 } 764 765 void SyncSetupHandler::HandleShowSetupUI(const base::ListValue* args) { 766 if (!GetSyncService()) { 767 DLOG(WARNING) << "Cannot display sync UI when sync is disabled"; 768 CloseUI(); 769 return; 770 } 771 772 SigninManagerBase* signin = 773 SigninManagerFactory::GetForProfile(GetProfile()); 774 if (!signin->IsAuthenticated()) { 775 // For web-based signin, the signin page is not displayed in an overlay 776 // on the settings page. So if we get here, it must be due to the user 777 // cancelling signin (by reloading the sync settings page during initial 778 // signin) or by directly navigating to settings/syncSetup 779 // (http://crbug.com/229836). So just exit and go back to the settings page. 780 DLOG(WARNING) << "Cannot display sync setup UI when not signed in"; 781 CloseUI(); 782 return; 783 } 784 785 // If a setup wizard is already present, but not on this page, close the 786 // blank setup overlay on this page by showing the "done" page. This can 787 // happen if the user navigates to chrome://settings/syncSetup in more than 788 // one tab. See crbug.com/261566. 789 // Note: The following block will transfer focus to the existing wizard. 790 if (IsExistingWizardPresent() && !IsActiveLogin()) { 791 CloseUI(); 792 } 793 794 // If a setup wizard is present on this page or another, bring it to focus. 795 // Otherwise, display a new one on this page. 796 if (!FocusExistingWizardIfPresent()) 797 OpenSyncSetup(); 798 } 799 800 #if defined(OS_CHROMEOS) 801 // On ChromeOS, we need to sign out the user session to fix an auth error, so 802 // the user goes through the real signin flow to generate a new auth token. 803 void SyncSetupHandler::HandleDoSignOutOnAuthError(const base::ListValue* args) { 804 DVLOG(1) << "Signing out the user to fix a sync error."; 805 chrome::AttemptUserExit(); 806 } 807 #endif 808 809 #if !defined(OS_CHROMEOS) 810 void SyncSetupHandler::HandleStartSignin(const base::ListValue* args) { 811 // Should only be called if the user is not already signed in. 812 DCHECK(!SigninManagerFactory::GetForProfile(GetProfile())-> 813 IsAuthenticated()); 814 OpenSyncSetup(); 815 } 816 817 void SyncSetupHandler::HandleStopSyncing(const base::ListValue* args) { 818 if (GetSyncService()) 819 ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS); 820 SigninManagerFactory::GetForProfile(GetProfile())->SignOut( 821 signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS); 822 823 bool delete_profile = false; 824 if (args->GetBoolean(0, &delete_profile) && delete_profile) { 825 // Do as BrowserOptionsHandler::DeleteProfile(). 826 options::helper::DeleteProfileAtPath(GetProfile()->GetPath(), web_ui()); 827 } 828 } 829 #endif 830 831 void SyncSetupHandler::HandleCloseTimeout(const base::ListValue* args) { 832 CloseSyncSetup(); 833 } 834 835 void SyncSetupHandler::CloseSyncSetup() { 836 // Stop a timer to handle timeout in waiting for checking network connection. 837 backend_start_timer_.reset(); 838 839 // Clear the sync startup tracker, since the setup wizard is being closed. 840 sync_startup_tracker_.reset(); 841 842 ProfileSyncService* sync_service = GetSyncService(); 843 if (IsActiveLogin()) { 844 // Don't log a cancel event if the sync setup dialog is being 845 // automatically closed due to an auth error. 846 if (!sync_service || (!sync_service->HasSyncSetupCompleted() && 847 sync_service->GetAuthError().state() == GoogleServiceAuthError::NONE)) { 848 if (configuring_sync_) { 849 ProfileSyncService::SyncEvent( 850 ProfileSyncService::CANCEL_DURING_CONFIGURE); 851 852 // If the user clicked "Cancel" while setting up sync, disable sync 853 // because we don't want the sync backend to remain in the 854 // first-setup-incomplete state. 855 // Note: In order to disable sync across restarts on Chrome OS, 856 // we must call StopSyncingPermanently(), which suppresses sync startup 857 // in addition to disabling it. 858 if (sync_service) { 859 DVLOG(1) << "Sync setup aborted by user action"; 860 sync_service->StopSyncingPermanently(); 861 #if !defined(OS_CHROMEOS) 862 // Sign out the user on desktop Chrome if they click cancel during 863 // initial setup. 864 // TODO(rsimha): Revisit this for M30. See http://crbug.com/252049. 865 if (sync_service->FirstSetupInProgress()) { 866 SigninManagerFactory::GetForProfile(GetProfile())->SignOut( 867 signin_metrics::ABORT_SIGNIN); 868 } 869 #endif 870 } 871 } 872 } 873 874 GetLoginUIService()->LoginUIClosed(this); 875 } 876 877 // Alert the sync service anytime the sync setup dialog is closed. This can 878 // happen due to the user clicking the OK or Cancel button, or due to the 879 // dialog being closed by virtue of sync being disabled in the background. 880 if (sync_service) 881 sync_service->SetSetupInProgress(false); 882 883 configuring_sync_ = false; 884 } 885 886 void SyncSetupHandler::OpenSyncSetup() { 887 if (!PrepareSyncSetup()) 888 return; 889 890 // There are several different UI flows that can bring the user here: 891 // 1) Signin promo. 892 // 2) Normal signin through settings page (IsAuthenticated() is false). 893 // 3) Previously working credentials have expired. 894 // 4) User is signed in, but has stopped sync via the google dashboard, and 895 // signout is prohibited by policy so we need to force a re-auth. 896 // 5) User clicks [Advanced Settings] button on options page while already 897 // logged in. 898 // 6) One-click signin (credentials are already available, so should display 899 // sync configure UI, not login UI). 900 // 7) User re-enables sync after disabling it via advanced settings. 901 #if !defined(OS_CHROMEOS) 902 SigninManagerBase* signin = 903 SigninManagerFactory::GetForProfile(GetProfile()); 904 905 if (!signin->IsAuthenticated() || 906 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile())-> 907 signin_error_controller()->HasError()) { 908 // User is not logged in (cases 1-2), or login has been specially requested 909 // because previously working credentials have expired (case 3). Close sync 910 // setup including any visible overlays, and display the gaia auth page. 911 // Control will be returned to the sync settings page once auth is complete. 912 CloseUI(); 913 DisplayGaiaLogin(); 914 return; 915 } 916 #endif 917 if (!GetSyncService()) { 918 // This can happen if the user directly navigates to /settings/syncSetup. 919 DLOG(WARNING) << "Cannot display sync UI when sync is disabled"; 920 CloseUI(); 921 return; 922 } 923 924 // User is already logged in. They must have brought up the config wizard 925 // via the "Advanced..." button or through One-Click signin (cases 4-6), or 926 // they are re-enabling sync after having disabled it (case 7). 927 DisplayConfigureSync(true, false); 928 } 929 930 void SyncSetupHandler::OpenConfigureSync() { 931 if (!PrepareSyncSetup()) 932 return; 933 934 DisplayConfigureSync(true, false); 935 } 936 937 void SyncSetupHandler::FocusUI() { 938 DCHECK(IsActiveLogin()); 939 WebContents* web_contents = web_ui()->GetWebContents(); 940 web_contents->GetDelegate()->ActivateContents(web_contents); 941 } 942 943 void SyncSetupHandler::CloseUI() { 944 CloseSyncSetup(); 945 base::StringValue page("done"); 946 web_ui()->CallJavascriptFunction( 947 "SyncSetupOverlay.showSyncSetupPage", page); 948 } 949 950 bool SyncSetupHandler::IsExistingWizardPresent() { 951 LoginUIService* service = GetLoginUIService(); 952 DCHECK(service); 953 return service->current_login_ui() != NULL; 954 } 955 956 bool SyncSetupHandler::FocusExistingWizardIfPresent() { 957 if (!IsExistingWizardPresent()) 958 return false; 959 960 LoginUIService* service = GetLoginUIService(); 961 DCHECK(service); 962 service->current_login_ui()->FocusUI(); 963 return true; 964 } 965 966 LoginUIService* SyncSetupHandler::GetLoginUIService() const { 967 return LoginUIServiceFactory::GetForProfile(GetProfile()); 968 } 969