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