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