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