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