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