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