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