1// Copyright (c) 2011 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/sync/sync_setup_flow.h"
6
7#include "base/callback.h"
8#include "base/json/json_reader.h"
9#include "base/json/json_writer.h"
10#include "base/metrics/histogram.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/platform_util.h"
15#include "chrome/browser/prefs/pref_service.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/sync/profile_sync_service.h"
18#include "chrome/browser/sync/sync_setup_flow_handler.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_dialogs.h"
21#include "chrome/browser/ui/browser_list.h"
22#include "chrome/common/net/gaia/google_service_auth_error.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/common/url_constants.h"
25#include "content/browser/renderer_host/render_view_host.h"
26#include "content/browser/tab_contents/tab_contents.h"
27#include "grit/generated_resources.h"
28#include "grit/locale_settings.h"
29#include "ui/base/l10n/l10n_font_util.h"
30#include "ui/gfx/font.h"
31
32namespace {
33
34// Helper function to disable password sync.
35void DisablePasswordSync(ProfileSyncService* service) {
36  syncable::ModelTypeSet types;
37  service->GetPreferredDataTypes(&types);
38  types.erase(syncable::PASSWORDS);
39  service->OnUserChoseDatatypes(false, types);
40}
41
42}  // namespace
43
44SyncConfiguration::SyncConfiguration()
45    : sync_everything(false),
46      use_secondary_passphrase(false) {
47}
48
49SyncConfiguration::~SyncConfiguration() {}
50
51SyncSetupFlow::~SyncSetupFlow() {
52  flow_handler_->SetFlow(NULL);
53}
54
55// static
56SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service,
57                                  SyncSetupFlowContainer* container,
58                                  SyncSetupWizard::State start,
59                                  SyncSetupWizard::State end) {
60  DictionaryValue args;
61  if (start == SyncSetupWizard::GAIA_LOGIN)
62    SyncSetupFlow::GetArgsForGaiaLogin(service, &args);
63  else if (start == SyncSetupWizard::CONFIGURE)
64    SyncSetupFlow::GetArgsForConfigure(service, &args);
65  else if (start == SyncSetupWizard::ENTER_PASSPHRASE)
66    SyncSetupFlow::GetArgsForEnterPassphrase(false, false, &args);
67  else if (start == SyncSetupWizard::PASSPHRASE_MIGRATION)
68    args.SetString("iframeToShow", "firstpassphrase");
69
70  std::string json_args;
71  base::JSONWriter::Write(&args, false, &json_args);
72
73  SyncSetupFlow* flow = new SyncSetupFlow(start, end, json_args,
74      container, service);
75
76  Browser* b = BrowserList::GetLastActive();
77  b->ShowOptionsTab(chrome::kSyncSetupSubPage);
78  return flow;
79}
80
81// static
82void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service,
83                                        DictionaryValue* args) {
84  args->SetString("iframeToShow", "login");
85  const GoogleServiceAuthError& error = service->GetAuthError();
86  if (!service->last_attempted_user_email().empty()) {
87    args->SetString("user", service->last_attempted_user_email());
88    args->SetInteger("error", error.state());
89    args->SetBoolean("editable_user", true);
90  } else {
91    string16 user;
92    if (!service->cros_user().empty())
93      user = UTF8ToUTF16(service->cros_user());
94    else
95      user = service->GetAuthenticatedUsername();
96    args->SetString("user", user);
97    args->SetInteger("error", 0);
98    args->SetBoolean("editable_user", user.empty());
99  }
100
101  args->SetString("captchaUrl", error.captcha().image_url.spec());
102}
103
104// static
105void SyncSetupFlow::GetArgsForConfigure(ProfileSyncService* service,
106                                        DictionaryValue* args) {
107  args->SetString("iframeToShow", "configure");
108
109  // The SYNC_EVERYTHING case will set this to true.
110  args->SetBoolean("syncEverything", false);
111
112  args->SetBoolean("keepEverythingSynced",
113      service->profile()->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced));
114
115  // Bookmarks, Preferences, and Themes are launched for good, there's no
116  // going back now.  Check if the other data types are registered though.
117  syncable::ModelTypeSet registered_types;
118  service->GetRegisteredDataTypes(&registered_types);
119  args->SetBoolean("passwordsRegistered",
120      registered_types.count(syncable::PASSWORDS) > 0);
121  args->SetBoolean("autofillRegistered",
122      registered_types.count(syncable::AUTOFILL) > 0);
123  args->SetBoolean("extensionsRegistered",
124      registered_types.count(syncable::EXTENSIONS) > 0);
125  args->SetBoolean("typedUrlsRegistered",
126      registered_types.count(syncable::TYPED_URLS) > 0);
127  args->SetBoolean("appsRegistered",
128      registered_types.count(syncable::APPS) > 0);
129  args->SetBoolean("sessionsRegistered",
130      registered_types.count(syncable::SESSIONS) > 0);
131  args->SetBoolean("syncBookmarks",
132      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks));
133  args->SetBoolean("syncPreferences",
134      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences));
135  args->SetBoolean("syncThemes",
136      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes));
137  args->SetBoolean("syncPasswords",
138      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords));
139  args->SetBoolean("syncAutofill",
140      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill));
141  args->SetBoolean("syncExtensions",
142      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions));
143  args->SetBoolean("syncSessions",
144      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions));
145  args->SetBoolean("syncTypedUrls",
146      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls));
147  args->SetBoolean("syncApps",
148      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncApps));
149
150  // Load the parameters for the encryption tab.
151  args->SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase());
152}
153
154// static
155void SyncSetupFlow::GetArgsForEnterPassphrase(
156    bool tried_creating_explicit_passphrase,
157    bool tried_setting_explicit_passphrase,
158    DictionaryValue* args) {
159  args->SetString("iframeToShow", "passphrase");
160  args->SetBoolean("passphrase_creation_rejected",
161                   tried_creating_explicit_passphrase);
162  args->SetBoolean("passphrase_setting_rejected",
163                   tried_setting_explicit_passphrase);
164}
165
166void SyncSetupFlow::AttachSyncSetupHandler(SyncSetupFlowHandler* handler) {
167  flow_handler_ = handler;
168  ActivateState(current_state_);
169}
170
171void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) {
172  if (!ShouldAdvance(advance_state)) {
173    LOG(WARNING) << "Invalid state change from "
174                 << current_state_ << " to " << advance_state;
175    return;
176  }
177
178  ActivateState(advance_state);
179}
180
181void SyncSetupFlow::Focus() {
182  // TODO(jhawkins): Implement this.
183}
184
185// A callback to notify the delegate that the dialog closed.
186void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) {
187  DCHECK(json_retval.empty());
188  container_->set_flow(NULL);  // Sever ties from the wizard.
189  if (current_state_ == SyncSetupWizard::DONE ||
190      current_state_ == SyncSetupWizard::DONE_FIRST_TIME) {
191    service_->SetSyncSetupCompleted();
192  }
193
194  // Record the state at which the user cancelled the signon dialog.
195  switch (current_state_) {
196    case SyncSetupWizard::GAIA_LOGIN:
197      ProfileSyncService::SyncEvent(
198          ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
199      break;
200    case SyncSetupWizard::GAIA_SUCCESS:
201      ProfileSyncService::SyncEvent(
202          ProfileSyncService::CANCEL_DURING_SIGNON);
203      break;
204    case SyncSetupWizard::CONFIGURE:
205    case SyncSetupWizard::ENTER_PASSPHRASE:
206    case SyncSetupWizard::SETTING_UP:
207      // TODO(atwilson): Treat a close during ENTER_PASSPHRASE like a
208      // Cancel + Skip (i.e. call OnPassphraseCancel()). http://crbug.com/74645
209      ProfileSyncService::SyncEvent(
210          ProfileSyncService::CANCEL_DURING_CONFIGURE);
211      break;
212    case SyncSetupWizard::DONE_FIRST_TIME:
213    case SyncSetupWizard::DONE:
214      // TODO(sync): rename this histogram; it's tracking authorization AND
215      // initial sync download time.
216      UMA_HISTOGRAM_MEDIUM_TIMES("Sync.UserPerceivedAuthorizationTime",
217                                 base::TimeTicks::Now() - login_start_time_);
218      break;
219    default:
220      break;
221  }
222
223  service_->OnUserCancelledDialog();
224  delete this;
225}
226
227void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username,
228                                        const std::string& password,
229                                        const std::string& captcha,
230                                        const std::string& access_code) {
231  service_->OnUserSubmittedAuth(username, password, captcha, access_code);
232}
233
234void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) {
235  // Go to the "loading..." screen.
236  Advance(SyncSetupWizard::SETTING_UP);
237
238  // If we are activating the passphrase, we need to have one supplied.
239  DCHECK(service_->IsUsingSecondaryPassphrase() ||
240         !configuration.use_secondary_passphrase ||
241         configuration.secondary_passphrase.length() > 0);
242
243  if (configuration.use_secondary_passphrase &&
244      !service_->IsUsingSecondaryPassphrase()) {
245    service_->SetPassphrase(configuration.secondary_passphrase, true, true);
246    tried_creating_explicit_passphrase_ = true;
247  }
248
249  service_->OnUserChoseDatatypes(configuration.sync_everything,
250                                 configuration.data_types);
251}
252
253void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase) {
254  Advance(SyncSetupWizard::SETTING_UP);
255  service_->SetPassphrase(passphrase, true, false);
256  tried_setting_explicit_passphrase_ = true;
257}
258
259void SyncSetupFlow::OnPassphraseCancel() {
260  // If the user cancels when being asked for the passphrase,
261  // just disable encrypted sync and continue setting up.
262  if (current_state_ == SyncSetupWizard::ENTER_PASSPHRASE)
263    DisablePasswordSync(service_);
264
265  Advance(SyncSetupWizard::SETTING_UP);
266}
267
268// TODO(jhawkins): Remove this method.
269void SyncSetupFlow::OnFirstPassphraseEntry(const std::string& option,
270                                           const std::string& passphrase) {
271  NOTREACHED();
272}
273
274// TODO(jhawkins): Use this method instead of a direct link in the html.
275void SyncSetupFlow::OnGoToDashboard() {
276  BrowserList::GetLastActive()->OpenPrivacyDashboardTabAndActivate();
277}
278
279// Use static Run method to get an instance.
280SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state,
281                             SyncSetupWizard::State end_state,
282                             const std::string& args,
283                             SyncSetupFlowContainer* container,
284                             ProfileSyncService* service)
285    : container_(container),
286      dialog_start_args_(args),
287      current_state_(start_state),
288      end_state_(end_state),
289      login_start_time_(base::TimeTicks::Now()),
290      flow_handler_(NULL),
291      service_(service),
292      tried_creating_explicit_passphrase_(false),
293      tried_setting_explicit_passphrase_(false) {
294}
295
296// Returns true if the flow should advance to |state| based on |current_state_|.
297bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) {
298  switch (state) {
299    case SyncSetupWizard::GAIA_LOGIN:
300      return current_state_ == SyncSetupWizard::FATAL_ERROR ||
301             current_state_ == SyncSetupWizard::GAIA_LOGIN ||
302             current_state_ == SyncSetupWizard::SETTING_UP;
303    case SyncSetupWizard::GAIA_SUCCESS:
304      return current_state_ == SyncSetupWizard::GAIA_LOGIN;
305    case SyncSetupWizard::SYNC_EVERYTHING:
306    case SyncSetupWizard::CONFIGURE:
307      return current_state_ == SyncSetupWizard::GAIA_SUCCESS;
308    case SyncSetupWizard::ENTER_PASSPHRASE:
309      return current_state_ == SyncSetupWizard::SYNC_EVERYTHING ||
310             current_state_ == SyncSetupWizard::CONFIGURE ||
311             current_state_ == SyncSetupWizard::SETTING_UP;
312    case SyncSetupWizard::PASSPHRASE_MIGRATION:
313      return current_state_ == SyncSetupWizard::GAIA_LOGIN;
314    case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR:
315      DCHECK(current_state_ != SyncSetupWizard::GAIA_LOGIN &&
316             current_state_ != SyncSetupWizard::GAIA_SUCCESS);
317      return true;
318    case SyncSetupWizard::SETTING_UP:
319      return current_state_ == SyncSetupWizard::SYNC_EVERYTHING ||
320             current_state_ == SyncSetupWizard::CONFIGURE ||
321             current_state_ == SyncSetupWizard::ENTER_PASSPHRASE ||
322             current_state_ == SyncSetupWizard::PASSPHRASE_MIGRATION;
323    case SyncSetupWizard::FATAL_ERROR:
324      return true;  // You can always hit the panic button.
325    case SyncSetupWizard::DONE_FIRST_TIME:
326    case SyncSetupWizard::DONE:
327      return current_state_ == SyncSetupWizard::SETTING_UP ||
328             current_state_ == SyncSetupWizard::ENTER_PASSPHRASE;
329    default:
330      NOTREACHED() << "Unhandled State: " << state;
331      return false;
332  }
333}
334
335void SyncSetupFlow::ActivateState(SyncSetupWizard::State state) {
336  switch (state) {
337    case SyncSetupWizard::GAIA_LOGIN: {
338      DictionaryValue args;
339      SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
340      flow_handler_->ShowGaiaLogin(args);
341      break;
342    }
343    case SyncSetupWizard::GAIA_SUCCESS:
344      if (end_state_ == SyncSetupWizard::GAIA_SUCCESS) {
345        flow_handler_->ShowGaiaSuccessAndClose();
346        break;
347      }
348      state = SyncSetupWizard::SYNC_EVERYTHING;
349      //  Fall through.
350    case SyncSetupWizard::SYNC_EVERYTHING: {
351      DictionaryValue args;
352      SyncSetupFlow::GetArgsForConfigure(service_, &args);
353      args.SetBoolean("syncEverything", true);
354      flow_handler_->ShowConfigure(args);
355      break;
356    }
357    case SyncSetupWizard::CONFIGURE: {
358      DictionaryValue args;
359      SyncSetupFlow::GetArgsForConfigure(service_, &args);
360      flow_handler_->ShowConfigure(args);
361      break;
362    }
363    case SyncSetupWizard::ENTER_PASSPHRASE: {
364      DictionaryValue args;
365      SyncSetupFlow::GetArgsForEnterPassphrase(
366          tried_creating_explicit_passphrase_,
367          tried_setting_explicit_passphrase_,
368          &args);
369      flow_handler_->ShowPassphraseEntry(args);
370      break;
371    }
372    case SyncSetupWizard::PASSPHRASE_MIGRATION: {
373      DictionaryValue args;
374      args.SetString("iframeToShow", "firstpassphrase");
375      flow_handler_->ShowFirstPassphrase(args);
376      break;
377    }
378    case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: {
379      DictionaryValue args;
380      SyncSetupFlow::GetArgsForConfigure(service_, &args);
381      args.SetBoolean("was_aborted", true);
382      flow_handler_->ShowConfigure(args);
383      break;
384    }
385    case SyncSetupWizard::SETTING_UP: {
386      flow_handler_->ShowSettingUp();
387      break;
388    }
389    case SyncSetupWizard::FATAL_ERROR: {
390      // This shows the user the "Could not connect to server" error.
391      // TODO(sync): Update this error messaging.
392      DictionaryValue args;
393      SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
394      args.SetInteger("error", GoogleServiceAuthError::CONNECTION_FAILED);
395      flow_handler_->ShowGaiaLogin(args);
396      break;
397    }
398    case SyncSetupWizard::DONE_FIRST_TIME:
399      flow_handler_->ShowFirstTimeDone(
400          UTF16ToWide(service_->GetAuthenticatedUsername()));
401      break;
402    case SyncSetupWizard::DONE:
403      flow_handler_->ShowSetupDone(
404          UTF16ToWide(service_->GetAuthenticatedUsername()));
405      break;
406    default:
407      NOTREACHED() << "Invalid advance state: " << state;
408  }
409  current_state_ = state;
410}
411