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