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