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