existing_user_controller.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 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/chromeos/login/existing_user_controller.h"
6
7#include <algorithm>
8#include <functional>
9#include <map>
10
11#include "app/l10n_util.h"
12#include "app/resource_bundle.h"
13#include "base/command_line.h"
14#include "base/message_loop.h"
15#include "base/stl_util-inl.h"
16#include "base/utf_string_conversions.h"
17#include "base/values.h"
18#include "chrome/browser/chromeos/boot_times_loader.h"
19#include "chrome/browser/chromeos/cros/cros_library.h"
20#include "chrome/browser/chromeos/cros/cryptohome_library.h"
21#include "chrome/browser/chromeos/cros/login_library.h"
22#include "chrome/browser/chromeos/cros/network_library.h"
23#include "chrome/browser/chromeos/login/background_view.h"
24#include "chrome/browser/chromeos/login/help_app_launcher.h"
25#include "chrome/browser/chromeos/login/helper.h"
26#include "chrome/browser/chromeos/login/login_utils.h"
27#include "chrome/browser/chromeos/login/message_bubble.h"
28#include "chrome/browser/chromeos/login/wizard_controller.h"
29#include "chrome/browser/chromeos/status/status_area_view.h"
30#include "chrome/browser/chromeos/user_cros_settings_provider.h"
31#include "chrome/browser/chromeos/view_ids.h"
32#include "chrome/browser/chromeos/wm_ipc.h"
33#include "chrome/browser/views/window.h"
34#include "chrome/common/chrome_switches.h"
35#include "chrome/common/net/gaia/google_service_auth_error.h"
36#include "gfx/native_widget_types.h"
37#include "grit/chromium_strings.h"
38#include "grit/generated_resources.h"
39#include "grit/theme_resources.h"
40#include "views/screen.h"
41#include "views/widget/widget_gtk.h"
42#include "views/window/window.h"
43
44namespace chromeos {
45
46namespace {
47
48// Max number of users we'll show. The true max is the min of this and the
49// number of windows that fit on the screen.
50const size_t kMaxUsers = 5;
51
52// Used to indicate no user has been selected.
53const size_t kNotSelected = -1;
54
55// Offset of cursor in first position from edit left side. It's used to position
56// info bubble arrow to cursor.
57const int kCursorOffset = 5;
58
59// Used to handle the asynchronous response of deleting a cryptohome directory.
60class RemoveAttempt : public CryptohomeLibrary::Delegate {
61 public:
62  explicit RemoveAttempt(const std::string& user_email)
63      : user_email_(user_email) {
64    if (CrosLibrary::Get()->EnsureLoaded()) {
65      CrosLibrary::Get()->GetCryptohomeLibrary()->AsyncRemove(
66          user_email_, this);
67    }
68  }
69
70  void OnComplete(bool success, int return_code) {
71    // Log the error, but there's not much we can do.
72    if (!success) {
73      VLOG(1) << "Removal of cryptohome for " << user_email_
74              << " failed, return code: " << return_code;
75    }
76    delete this;
77  }
78
79 private:
80  std::string user_email_;
81};
82
83// Checks if display names are unique. If there are duplicates, enables
84// tooltips with full emails to let users distinguish their accounts.
85// Otherwise, disables the tooltips.
86void EnableTooltipsIfNeeded(const std::vector<UserController*>& controllers) {
87  std::map<std::string, int> visible_display_names;
88  for (size_t i = 0; i + 1 < controllers.size(); ++i) {
89    const std::string& display_name =
90        controllers[i]->user().GetDisplayName();
91    ++visible_display_names[display_name];
92  }
93  for (size_t i = 0; i < controllers.size(); ++i) {
94    const std::string& display_name =
95        controllers[i]->user().GetDisplayName();
96    bool show_tooltip = controllers[i]->is_new_user() ||
97                        controllers[i]->is_guest() ||
98                        visible_display_names[display_name] > 1;
99    controllers[i]->EnableNameTooltip(show_tooltip);
100  }
101}
102
103}  // namespace
104
105ExistingUserController*
106  ExistingUserController::delete_scheduled_instance_ = NULL;
107
108// TODO(xiyuan): Wait for the cached settings update before using them.
109ExistingUserController::ExistingUserController(
110    const std::vector<UserManager::User>& users,
111    const gfx::Rect& background_bounds)
112    : background_bounds_(background_bounds),
113      background_window_(NULL),
114      background_view_(NULL),
115      selected_view_index_(kNotSelected),
116      num_login_attempts_(0),
117      bubble_(NULL),
118      user_settings_(new UserCrosSettingsProvider()) {
119  if (delete_scheduled_instance_)
120    delete_scheduled_instance_->Delete();
121
122  // Caclulate the max number of users from available screen size.
123  if (UserCrosSettingsProvider::cached_show_users_on_signin()) {
124    size_t max_users = kMaxUsers;
125    int screen_width = background_bounds.width();
126    if (screen_width > 0) {
127      max_users = std::max(static_cast<size_t>(2), std::min(kMaxUsers,
128          static_cast<size_t>((screen_width - login::kUserImageSize)
129                              / (UserController::kUnselectedSize +
130                                 UserController::kPadding))));
131    }
132
133    size_t visible_users_count = std::min(users.size(), max_users - 1);
134    for (size_t i = 0; i < users.size(); ++i) {
135      if (controllers_.size() == visible_users_count)
136        break;
137
138      // TODO(xiyuan): Clean user profile whose email is not in whitelist.
139      if (UserCrosSettingsProvider::cached_allow_new_user() ||
140          UserCrosSettingsProvider::IsEmailInCachedWhitelist(
141              users[i].email())) {
142        controllers_.push_back(new UserController(this, users[i]));
143      }
144    }
145  }
146
147  if (!controllers_.empty() && UserCrosSettingsProvider::cached_allow_guest())
148    controllers_.push_back(new UserController(this, true));
149
150  // Add the view representing the new user.
151  controllers_.push_back(new UserController(this, false));
152}
153
154void ExistingUserController::Init() {
155  if (!background_window_) {
156    std::string url_string =
157        CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
158            switches::kScreenSaverUrl);
159
160    background_window_ = BackgroundView::CreateWindowContainingView(
161        background_bounds_,
162        GURL(url_string),
163        &background_view_);
164    background_view_->EnableShutdownButton();
165
166    if (!WizardController::IsDeviceRegistered()) {
167      background_view_->SetOobeProgressBarVisible(true);
168      background_view_->SetOobeProgress(chromeos::BackgroundView::SIGNIN);
169    }
170
171    background_window_->Show();
172  }
173  // If there's only new user pod, show the guest session link on it.
174  bool show_guest_link = controllers_.size() == 1;
175  for (size_t i = 0; i < controllers_.size(); ++i) {
176    (controllers_[i])->Init(static_cast<int>(i),
177                            static_cast<int>(controllers_.size()),
178                            show_guest_link);
179  }
180
181  EnableTooltipsIfNeeded(controllers_);
182
183  WmMessageListener::instance()->AddObserver(this);
184
185  LoginUtils::Get()->PrewarmAuthentication();
186  if (CrosLibrary::Get()->EnsureLoaded())
187    CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady();
188}
189
190void ExistingUserController::OwnBackground(
191    views::Widget* background_widget,
192    chromeos::BackgroundView* background_view) {
193  DCHECK(!background_window_);
194  background_window_ = background_widget;
195  background_view_ = background_view;
196}
197
198void ExistingUserController::LoginNewUser(const std::string& username,
199                                          const std::string& password) {
200  SelectNewUser();
201  UserController* new_user = controllers_.back();
202  DCHECK(new_user->is_new_user());
203  if (!new_user->is_new_user())
204    return;
205  NewUserView* new_user_view = new_user->new_user_view();
206  new_user_view->SetUsername(username);
207
208  if (password.empty())
209    return;
210
211  new_user_view->SetPassword(password);
212  new_user_view->Login();
213}
214
215void ExistingUserController::SelectNewUser() {
216  SelectUser(controllers_.size() - 1);
217}
218
219void ExistingUserController::SetStatusAreaEnabled(bool enable) {
220  if (background_view_) {
221    background_view_->SetStatusAreaEnabled(enable);
222  }
223}
224
225ExistingUserController::~ExistingUserController() {
226  ClearErrors();
227
228  if (background_window_)
229    background_window_->Close();
230
231  WmMessageListener::instance()->RemoveObserver(this);
232
233  STLDeleteElements(&controllers_);
234}
235
236void ExistingUserController::Delete() {
237  delete_scheduled_instance_ = NULL;
238  delete this;
239}
240
241void ExistingUserController::ProcessWmMessage(const WmIpc::Message& message,
242                                              GdkWindow* window) {
243  if (message.type() != WM_IPC_MESSAGE_CHROME_CREATE_GUEST_WINDOW)
244    return;
245
246  ActivateWizard(std::string());
247}
248
249void ExistingUserController::SendSetLoginState(bool is_enabled) {
250  WmIpc::Message message(WM_IPC_MESSAGE_WM_SET_LOGIN_STATE);
251  message.set_param(0, is_enabled);
252  WmIpc::instance()->SendMessage(message);
253}
254
255void ExistingUserController::Login(UserController* source,
256                                   const string16& password) {
257  BootTimesLoader::Get()->RecordLoginAttempted();
258  std::vector<UserController*>::const_iterator i =
259      std::find(controllers_.begin(), controllers_.end(), source);
260  DCHECK(i != controllers_.end());
261
262  if (i == controllers_.begin() + selected_view_index_) {
263    num_login_attempts_++;
264  } else {
265    selected_view_index_ = i - controllers_.begin();
266    num_login_attempts_ = 0;
267  }
268
269  // Disable clicking on other windows.
270  SendSetLoginState(false);
271
272  // Use the same LoginPerformer for subsequent login as it has state
273  // such as CAPTCHA challenge token & corresponding user input.
274  if (!login_performer_.get() || num_login_attempts_ <= 1) {
275    login_performer_.reset(new LoginPerformer(this));
276  }
277  login_performer_->Login(controllers_[selected_view_index_]->user().email(),
278                          UTF16ToUTF8(password));
279}
280
281void ExistingUserController::WhiteListCheckFailed(const std::string& email) {
282  ShowError(IDS_LOGIN_ERROR_WHITELIST, email);
283
284  // Reenable userview and use ClearAndEnablePassword to keep username on
285  // screen with the error bubble.
286  controllers_[selected_view_index_]->ClearAndEnablePassword();
287
288  // Reenable clicking on other windows.
289  SendSetLoginState(true);
290}
291
292void ExistingUserController::LoginOffTheRecord() {
293  // Check allow_guest in case this call is fired from key accelerator.
294  if (!UserCrosSettingsProvider::cached_allow_guest())
295    return;
296
297  // Disable clicking on other windows.
298  SendSetLoginState(false);
299
300  login_performer_.reset(new LoginPerformer(this));
301  login_performer_->LoginOffTheRecord();
302}
303
304void ExistingUserController::ClearErrors() {
305  // bubble_ will be set to NULL in callback.
306  if (bubble_)
307    bubble_->Close();
308}
309
310void ExistingUserController::OnUserSelected(UserController* source) {
311  std::vector<UserController*>::const_iterator i =
312      std::find(controllers_.begin(), controllers_.end(), source);
313  DCHECK(i != controllers_.end());
314  size_t new_selected_index = i - controllers_.begin();
315  if (new_selected_index != selected_view_index_ &&
316      selected_view_index_ != kNotSelected) {
317    controllers_[selected_view_index_]->ClearAndEnableFields();
318    controllers_[new_selected_index]->ClearAndEnableFields();
319    login_performer_.reset(NULL);
320    num_login_attempts_ = 0;
321  }
322  selected_view_index_ = new_selected_index;
323}
324
325void ExistingUserController::ActivateWizard(const std::string& screen_name) {
326  // WizardController takes care of deleting itself when done.
327  WizardController* controller = new WizardController();
328
329  // Give the background window to the controller.
330  controller->OwnBackground(background_window_, background_view_);
331  background_window_ = NULL;
332
333  controller->Init(screen_name, background_bounds_);
334  controller->set_start_url(start_url_);
335  controller->Show();
336
337  // And schedule us for deletion. We delay for a second as the window manager
338  // is doing an animation with our windows.
339  DCHECK(!delete_scheduled_instance_);
340  delete_scheduled_instance_ = this;
341  delete_timer_.Start(base::TimeDelta::FromSeconds(1), this,
342                      &ExistingUserController::Delete);
343}
344
345void ExistingUserController::RemoveUser(UserController* source) {
346  ClearErrors();
347
348  // TODO(xiyuan): Wait for the cached settings update before using them.
349  if (UserCrosSettingsProvider::cached_owner() == source->user().email()) {
350    // Owner is not allowed to be removed from the device.
351    return;
352  }
353
354  UserManager::Get()->RemoveUser(source->user().email());
355
356  controllers_.erase(controllers_.begin() + source->user_index());
357
358  EnableTooltipsIfNeeded(controllers_);
359
360  // Update user count before unmapping windows, otherwise window manager won't
361  // be in the right state.
362  int new_size = static_cast<int>(controllers_.size());
363  for (int i = 0; i < new_size; ++i)
364    controllers_[i]->UpdateUserCount(i, new_size);
365
366  // Delete the encrypted user directory.
367  new RemoveAttempt(source->user().email());
368  // We need to unmap entry windows, the windows will be unmapped in destructor.
369  delete source;
370}
371
372void ExistingUserController::SelectUser(int index) {
373  if (index >= 0 && index < static_cast<int>(controllers_.size()) &&
374      index != static_cast<int>(selected_view_index_)) {
375    WmIpc::Message message(WM_IPC_MESSAGE_WM_SELECT_LOGIN_USER);
376    message.set_param(0, index);
377    WmIpc::instance()->SendMessage(message);
378  }
379}
380
381void ExistingUserController::OnLoginFailure(const LoginFailure& failure) {
382  std::string error = failure.GetErrorString();
383
384  // Check networking after trying to login in case user is
385  // cached locally or the local admin account.
386  NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary();
387  if (!network || !CrosLibrary::Get()->EnsureLoaded()) {
388    ShowError(IDS_LOGIN_ERROR_NO_NETWORK_LIBRARY, error);
389  } else if (!network->Connected()) {
390    ShowError(IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED, error);
391  } else {
392    if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
393        failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) {
394      if (!failure.error().captcha().image_url.is_empty()) {
395        CaptchaView* view =
396            new CaptchaView(failure.error().captcha().image_url);
397        view->set_delegate(this);
398        views::Window* window = browser::CreateViewsWindow(
399            GetNativeWindow(), gfx::Rect(), view);
400        window->SetIsAlwaysOnTop(true);
401        window->Show();
402      } else {
403        LOG(WARNING) << "No captcha image url was found?";
404        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
405      }
406    } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
407               failure.error().state() ==
408                   GoogleServiceAuthError::HOSTED_NOT_ALLOWED) {
409      ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED, error);
410    } else {
411      if (controllers_[selected_view_index_]->is_new_user())
412        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_NEW, error);
413      else
414        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
415    }
416  }
417
418  controllers_[selected_view_index_]->ClearAndEnablePassword();
419
420  // Reenable clicking on other windows.
421  SendSetLoginState(true);
422}
423
424void ExistingUserController::AppendStartUrlToCmdline() {
425  if (start_url_.is_valid())
426    CommandLine::ForCurrentProcess()->AppendArg(start_url_.spec());
427}
428
429gfx::NativeWindow ExistingUserController::GetNativeWindow() const {
430  return GTK_WINDOW(
431      static_cast<views::WidgetGtk*>(background_window_)->GetNativeView());
432}
433
434void ExistingUserController::ShowError(int error_id,
435                                       const std::string& details) {
436  ClearErrors();
437  std::wstring error_text;
438  // GetStringF fails on debug build if there's no replacement in the string.
439  if (error_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) {
440    error_text = l10n_util::GetStringF(
441        error_id, l10n_util::GetString(IDS_PRODUCT_OS_NAME));
442  } else {
443    error_text = l10n_util::GetString(error_id);
444  }
445  // TODO(dpolukhin): show detailed error info. |details| string contains
446  // low level error info that is not localized and even is not user friendly.
447  // For now just ignore it because error_text contains all required information
448  // for end users, developers can see details string in Chrome logs.
449
450  gfx::Rect bounds = controllers_[selected_view_index_]->GetScreenBounds();
451  BubbleBorder::ArrowLocation arrow;
452  if (controllers_[selected_view_index_]->is_new_user()) {
453    arrow = BubbleBorder::LEFT_TOP;
454  } else {
455    // Point info bubble arrow to cursor position (approximately).
456    bounds.set_width(kCursorOffset * 2);
457    arrow = BubbleBorder::BOTTOM_LEFT;
458  }
459  std::wstring help_link;
460  if (error_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) {
461    help_link = l10n_util::GetString(IDS_LEARN_MORE);
462  } else if (num_login_attempts_ > static_cast<size_t>(1)) {
463    help_link = l10n_util::GetString(IDS_CANT_ACCESS_ACCOUNT_BUTTON);
464  }
465
466  bubble_ = MessageBubble::Show(
467      controllers_[selected_view_index_]->controls_window(),
468      bounds,
469      arrow,
470      ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING),
471      error_text,
472      help_link,
473      this);
474}
475
476void ExistingUserController::OnLoginSuccess(
477    const std::string& username,
478    const std::string& password,
479    const GaiaAuthConsumer::ClientLoginResult& credentials,
480    bool pending_requests) {
481  // LoginPerformer instance will delete itself once online auth result is OK.
482  // In case of failure it'll bring up ScreenLock and ask for
483  // correct password/display error message.
484  // Even in case when following online,offline protocol and returning
485  // requests_pending = false, let LoginPerformer delete itself.
486  login_performer_->set_delegate(NULL);
487  LoginPerformer* performer = login_performer_.release();
488  performer = NULL;
489  AppendStartUrlToCmdline();
490  if (selected_view_index_ + 1 == controllers_.size() &&
491      !UserManager::Get()->IsKnownUser(username)) {
492    // For new user login don't launch browser until we pass image screen.
493    LoginUtils::Get()->EnableBrowserLaunch(false);
494    LoginUtils::Get()->CompleteLogin(username, password, credentials);
495    ActivateWizard(WizardController::IsDeviceRegistered() ?
496        WizardController::kUserImageScreenName :
497        WizardController::kRegistrationScreenName);
498  } else {
499    // Hide the login windows now.
500    WmIpc::Message message(WM_IPC_MESSAGE_WM_HIDE_LOGIN);
501    WmIpc::instance()->SendMessage(message);
502
503    LoginUtils::Get()->CompleteLogin(username, password, credentials);
504
505    // Delay deletion as we're on the stack.
506    MessageLoop::current()->DeleteSoon(FROM_HERE, this);
507  }
508}
509
510void ExistingUserController::OnOffTheRecordLoginSuccess() {
511  if (WizardController::IsDeviceRegistered()) {
512    LoginUtils::Get()->CompleteOffTheRecordLogin(start_url_);
513  } else {
514    // Postpone CompleteOffTheRecordLogin until registration completion.
515    ActivateWizard(WizardController::kRegistrationScreenName);
516  }
517}
518
519void ExistingUserController::OnPasswordChangeDetected(
520    const GaiaAuthConsumer::ClientLoginResult& credentials) {
521  // When signing in as a "New user" always remove old cryptohome.
522  if (selected_view_index_ == controllers_.size() - 1) {
523    login_performer_->ResyncEncryptedData();
524    return;
525  }
526
527  // TODO(altimofeev): remove this constrain when full sync for the owner will
528  // be correctly handled.
529  // TODO(xiyuan): Wait for the cached settings update before using them.
530  bool full_sync_disabled = (UserCrosSettingsProvider::cached_owner() ==
531      controllers_[selected_view_index_]->user().email());
532
533  PasswordChangedView* view = new PasswordChangedView(this, full_sync_disabled);
534  views::Window* window = browser::CreateViewsWindow(GetNativeWindow(),
535                                                     gfx::Rect(),
536                                                     view);
537  window->SetIsAlwaysOnTop(true);
538  window->Show();
539}
540
541void ExistingUserController::OnHelpLinkActivated() {
542  DCHECK(login_performer_->error().state() != GoogleServiceAuthError::NONE);
543  if (!help_app_.get())
544    help_app_.reset(new HelpAppLauncher(GetNativeWindow()));
545  switch (login_performer_->error().state()) {
546    case GoogleServiceAuthError::CONNECTION_FAILED:
547      help_app_->ShowHelpTopic(
548          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE);
549      break;
550    case GoogleServiceAuthError::ACCOUNT_DISABLED:
551        help_app_->ShowHelpTopic(
552            HelpAppLauncher::HELP_ACCOUNT_DISABLED);
553        break;
554    case GoogleServiceAuthError::HOSTED_NOT_ALLOWED:
555        help_app_->ShowHelpTopic(
556            HelpAppLauncher::HELP_HOSTED_ACCOUNT);
557        break;
558    default:
559      help_app_->ShowHelpTopic(login_performer_->login_timed_out() ?
560          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE :
561          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
562      break;
563  }
564}
565
566void ExistingUserController::OnCaptchaEntered(const std::string& captcha) {
567  login_performer_->set_captcha(captcha);
568}
569
570void ExistingUserController::RecoverEncryptedData(
571    const std::string& old_password) {
572  login_performer_->RecoverEncryptedData(old_password);
573}
574
575void ExistingUserController::ResyncEncryptedData() {
576  login_performer_->ResyncEncryptedData();
577}
578
579}  // namespace chromeos
580