existing_user_controller.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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/chromeos/login/existing_user_controller.h"
6
7#include "base/command_line.h"
8#include "base/message_loop.h"
9#include "base/utf_string_conversions.h"
10#include "base/values.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/chromeos/boot_times_loader.h"
13#include "chrome/browser/chromeos/cros/cros_library.h"
14#include "chrome/browser/chromeos/cros/cryptohome_library.h"
15#include "chrome/browser/chromeos/cros/login_library.h"
16#include "chrome/browser/chromeos/cros/network_library.h"
17#include "chrome/browser/chromeos/login/background_view.h"
18#include "chrome/browser/chromeos/login/helper.h"
19#include "chrome/browser/chromeos/login/login_utils.h"
20#include "chrome/browser/chromeos/login/views_login_display.h"
21#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
22#include "chrome/browser/chromeos/login/wizard_controller.h"
23#include "chrome/browser/chromeos/status/status_area_view.h"
24#include "chrome/browser/chromeos/user_cros_settings_provider.h"
25#include "chrome/browser/google/google_util.h"
26#include "chrome/browser/prefs/pref_service.h"
27#include "chrome/browser/profiles/profile_manager.h"
28#include "chrome/browser/ui/views/window.h"
29#include "chrome/common/chrome_switches.h"
30#include "chrome/common/net/gaia/google_service_auth_error.h"
31#include "chrome/common/notification_service.h"
32#include "chrome/common/notification_type.h"
33#include "chrome/common/pref_names.h"
34#include "grit/generated_resources.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "views/window/window.h"
37
38namespace chromeos {
39
40namespace {
41
42// Url for setting up sync authentication.
43const char kSettingsSyncLoginURL[] = "chrome://settings/personal";
44
45// URL that will be opened on when user logs in first time on the device.
46const char kGetStartedURL[] =
47    "chrome-extension://cbmhffdpiobpchciemffincgahkkljig/index.html";
48
49// URL for account creation.
50const char kCreateAccountURL[] =
51    "https://www.google.com/accounts/NewAccount?service=mail";
52
53// Landing URL when launching Guest mode to fix captive portal.
54const char kCaptivePortalLaunchURL[] = "http://www.google.com/";
55
56// Used to handle the asynchronous response of deleting a cryptohome directory.
57class RemoveAttempt : public CryptohomeLibrary::Delegate {
58 public:
59  explicit RemoveAttempt(const std::string& user_email)
60      : user_email_(user_email) {
61    if (CrosLibrary::Get()->EnsureLoaded()) {
62      CrosLibrary::Get()->GetCryptohomeLibrary()->AsyncRemove(
63          user_email_, this);
64    }
65  }
66
67  void OnComplete(bool success, int return_code) {
68    // Log the error, but there's not much we can do.
69    if (!success) {
70      VLOG(1) << "Removal of cryptohome for " << user_email_
71              << " failed, return code: " << return_code;
72    }
73    delete this;
74  }
75
76 private:
77  std::string user_email_;
78};
79
80}  // namespace
81
82// static
83ExistingUserController*
84  ExistingUserController::current_controller_ = NULL;
85
86////////////////////////////////////////////////////////////////////////////////
87// ExistingUserController, public:
88
89ExistingUserController::ExistingUserController(
90    const gfx::Rect& background_bounds)
91    : background_bounds_(background_bounds),
92      background_window_(NULL),
93      background_view_(NULL),
94      num_login_attempts_(0),
95      user_settings_(new UserCrosSettingsProvider),
96      method_factory_(this) {
97  if (current_controller_)
98    current_controller_->Delete();
99  current_controller_ = this;
100
101  login_display_.reset(CreateLoginDisplay(this, background_bounds));
102
103  registrar_.Add(this,
104                 NotificationType::LOGIN_USER_IMAGE_CHANGED,
105                 NotificationService::AllSources());
106}
107
108void ExistingUserController::Init(const UserVector& users) {
109  if (g_browser_process) {
110    PrefService* state = g_browser_process->local_state();
111    if (state) {
112      std::string owner_locale = state->GetString(prefs::kOwnerLocale);
113      // Ensure that we start with owner's locale.
114      if (!owner_locale.empty() &&
115          state->GetString(prefs::kApplicationLocale) != owner_locale &&
116          !state->IsManagedPreference(prefs::kApplicationLocale)) {
117        state->SetString(prefs::kApplicationLocale, owner_locale);
118        state->ScheduleSavePersistentPrefs();
119        LanguageSwitchMenu::SwitchLanguage(owner_locale);
120      }
121    }
122  }
123  if (!background_window_) {
124    background_window_ = BackgroundView::CreateWindowContainingView(
125        background_bounds_,
126        GURL(),
127        &background_view_);
128    background_view_->EnableShutdownButton(true);
129
130    if (!WizardController::IsDeviceRegistered()) {
131      background_view_->SetOobeProgressBarVisible(true);
132      background_view_->SetOobeProgress(chromeos::BackgroundView::SIGNIN);
133    }
134
135    background_window_->Show();
136  }
137
138  UserVector filtered_users;
139  if (UserCrosSettingsProvider::cached_show_users_on_signin()) {
140    for (size_t i = 0; i < users.size(); ++i)
141      // TODO(xiyuan): Clean user profile whose email is not in whitelist.
142      if (UserCrosSettingsProvider::cached_allow_new_user() ||
143          UserCrosSettingsProvider::IsEmailInCachedWhitelist(
144              users[i].email())) {
145        filtered_users.push_back(users[i]);
146      }
147  }
148
149  // If no user pods are visible, fallback to single new user pod which will
150  // have guest session link.
151  bool show_guest = UserCrosSettingsProvider::cached_allow_guest() &&
152                    !filtered_users.empty();
153  bool show_new_user = true;
154  login_display_->set_parent_window(GetNativeWindow());
155  login_display_->Init(filtered_users, show_guest, show_new_user);
156
157  LoginUtils::Get()->PrewarmAuthentication();
158  if (CrosLibrary::Get()->EnsureLoaded())
159    CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady();
160}
161
162void ExistingUserController::OwnBackground(
163    views::Widget* background_widget,
164    chromeos::BackgroundView* background_view) {
165  DCHECK(!background_window_);
166  background_window_ = background_widget;
167  background_view_ = background_view;
168}
169
170////////////////////////////////////////////////////////////////////////////////
171// ExistingUserController, NotificationObserver implementation:
172//
173
174void ExistingUserController::Observe(NotificationType type,
175                                     const NotificationSource& source,
176                                     const NotificationDetails& details) {
177  if (type != NotificationType::LOGIN_USER_IMAGE_CHANGED)
178    return;
179
180  UserManager::User* user = Details<UserManager::User>(details).ptr();
181  login_display_->OnUserImageChanged(user);
182}
183
184////////////////////////////////////////////////////////////////////////////////
185// ExistingUserController, private:
186
187ExistingUserController::~ExistingUserController() {
188  if (background_window_)
189    background_window_->Close();
190
191  DCHECK(current_controller_ != NULL);
192  current_controller_ = NULL;
193}
194
195////////////////////////////////////////////////////////////////////////////////
196// ExistingUserController, LoginDisplay::Delegate implementation:
197//
198
199void ExistingUserController::CreateAccount() {
200  guest_mode_url_ =
201      google_util::AppendGoogleLocaleParam(GURL(kCreateAccountURL));
202  LoginAsGuest();
203}
204
205void ExistingUserController::FixCaptivePortal() {
206  guest_mode_url_ = GURL(kCaptivePortalLaunchURL);
207  LoginAsGuest();
208}
209
210void ExistingUserController::Login(const std::string& username,
211                                   const std::string& password) {
212  if (username.empty() || password.empty())
213    return;
214  SetStatusAreaEnabled(false);
215  // Disable clicking on other windows.
216  login_display_->SetUIEnabled(false);
217
218  BootTimesLoader::Get()->RecordLoginAttempted();
219
220  if (last_login_attempt_username_ != username) {
221    last_login_attempt_username_ = username;
222    num_login_attempts_ = 0;
223  }
224  num_login_attempts_++;
225
226  // Use the same LoginPerformer for subsequent login as it has state
227  // such as CAPTCHA challenge token & corresponding user input.
228  if (!login_performer_.get() || num_login_attempts_ <= 1) {
229    LoginPerformer::Delegate* delegate = this;
230    if (login_performer_delegate_.get())
231      delegate = login_performer_delegate_.get();
232    // Only one instance of LoginPerformer should exist at a time.
233    login_performer_.reset(NULL);
234    login_performer_.reset(new LoginPerformer(delegate));
235  }
236  login_performer_->Login(username, password);
237  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
238      l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNING_IN).c_str(),
239      false, true);
240}
241
242void ExistingUserController::LoginAsGuest() {
243  SetStatusAreaEnabled(false);
244  // Disable clicking on other windows.
245  login_display_->SetUIEnabled(false);
246
247  // Check allow_guest in case this call is fired from key accelerator.
248  // Must not proceed without signature verification.
249  bool trusted_setting_available = user_settings_->RequestTrustedAllowGuest(
250      method_factory_.NewRunnableMethod(
251          &ExistingUserController::LoginAsGuest));
252  if (!trusted_setting_available) {
253    // Value of AllowGuest setting is still not verified.
254    // Another attempt will be invoked again after verification completion.
255    return;
256  }
257  if (!UserCrosSettingsProvider::cached_allow_guest()) {
258    // Disallowed.
259    return;
260  }
261
262  // Only one instance of LoginPerformer should exist at a time.
263  login_performer_.reset(NULL);
264  login_performer_.reset(new LoginPerformer(this));
265  login_performer_->LoginOffTheRecord();
266  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
267      l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_OFFRECORD).c_str(),
268      false, true);
269}
270
271void ExistingUserController::OnUserSelected(const std::string& username) {
272  login_performer_.reset(NULL);
273  num_login_attempts_ = 0;
274}
275
276void ExistingUserController::RemoveUser(const std::string& username) {
277  // Owner is not allowed to be removed from the device.
278  // Must not proceed without signature verification.
279  UserCrosSettingsProvider user_settings;
280  bool trusted_owner_available = user_settings.RequestTrustedOwner(
281      method_factory_.NewRunnableMethod(&ExistingUserController::RemoveUser,
282                                        username));
283  if (!trusted_owner_available) {
284    // Value of owner email is still not verified.
285    // Another attempt will be invoked after verification completion.
286    return;
287  }
288  if (username == UserCrosSettingsProvider::cached_owner()) {
289    // Owner is not allowed to be removed from the device.
290    return;
291  }
292
293  login_display_->OnBeforeUserRemoved(username);
294
295  // Delete user from user list.
296  UserManager::Get()->RemoveUser(username);
297
298  // Delete the encrypted user directory.
299  new RemoveAttempt(username);
300
301  login_display_->OnUserRemoved(username);
302}
303
304////////////////////////////////////////////////////////////////////////////////
305// ExistingUserController, LoginPerformer::Delegate implementation:
306//
307
308void ExistingUserController::OnLoginFailure(const LoginFailure& failure) {
309  guest_mode_url_ = GURL::EmptyGURL();
310  std::string error = failure.GetErrorString();
311
312  // Check networking after trying to login in case user is
313  // cached locally or the local admin account.
314  NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary();
315  if (!network || !CrosLibrary::Get()->EnsureLoaded()) {
316    ShowError(IDS_LOGIN_ERROR_NO_NETWORK_LIBRARY, error);
317  } else if (!network->Connected()) {
318    ShowError(IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED, error);
319  } else {
320    if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
321        failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) {
322      if (!failure.error().captcha().image_url.is_empty()) {
323        CaptchaView* view =
324            new CaptchaView(failure.error().captcha().image_url, false);
325        view->Init();
326        view->set_delegate(this);
327        views::Window* window = browser::CreateViewsWindow(
328            GetNativeWindow(), gfx::Rect(), view);
329        window->SetIsAlwaysOnTop(true);
330        window->Show();
331      } else {
332        LOG(WARNING) << "No captcha image url was found?";
333        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
334      }
335    } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
336               failure.error().state() ==
337                   GoogleServiceAuthError::HOSTED_NOT_ALLOWED) {
338      ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED, error);
339    } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
340               failure.error().state() ==
341                   GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
342      // SERVICE_UNAVAILABLE is generated in 2 cases:
343      // 1. ClientLogin returns ServiceUnavailable code.
344      // 2. Internet connectivity may be behind the captive portal.
345      // Suggesting user to try sign in to a portal in Guest mode.
346      ShowError(IDS_LOGIN_ERROR_CAPTIVE_PORTAL, error);
347    } else {
348      if (!UserManager::Get()->IsKnownUser(last_login_attempt_username_))
349        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_NEW, error);
350      else
351        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
352    }
353  }
354
355  // Reenable clicking on other windows and status area.
356  login_display_->SetUIEnabled(true);
357  SetStatusAreaEnabled(true);
358}
359
360void ExistingUserController::OnLoginSuccess(
361    const std::string& username,
362    const std::string& password,
363    const GaiaAuthConsumer::ClientLoginResult& credentials,
364    bool pending_requests) {
365  // LoginPerformer instance will delete itself once online auth result is OK.
366  // In case of failure it'll bring up ScreenLock and ask for
367  // correct password/display error message.
368  // Even in case when following online,offline protocol and returning
369  // requests_pending = false, let LoginPerformer delete itself.
370  login_performer_->set_delegate(NULL);
371  LoginPerformer* performer = login_performer_.release();
372  performer = NULL;
373  bool known_user = UserManager::Get()->IsKnownUser(username);
374  bool login_only =
375      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
376          switches::kLoginScreen) == WizardController::kLoginScreenName;
377  // TODO(nkostylev): May add login UI implementation callback call.
378  if (!known_user && !login_only) {
379#if defined(OFFICIAL_BUILD)
380    CommandLine::ForCurrentProcess()->AppendArg(kGetStartedURL);
381#endif  // OFFICIAL_BUILD
382
383    if (credentials.two_factor) {
384      // If we have a two factor error and and this is a new user,
385      // load the personal settings page.
386      // TODO(stevenjb): direct the user to a lightweight sync login page.
387      CommandLine::ForCurrentProcess()->AppendArg(kSettingsSyncLoginURL);
388    }
389
390    // For new user login don't launch browser until we pass image screen.
391    LoginUtils::Get()->EnableBrowserLaunch(false);
392    LoginUtils::Get()->CompleteLogin(username,
393                                     password,
394                                     credentials,
395                                     pending_requests);
396
397    ActivateWizard(WizardController::IsDeviceRegistered() ?
398        WizardController::kUserImageScreenName :
399        WizardController::kRegistrationScreenName);
400  } else {
401    LoginUtils::Get()->CompleteLogin(username,
402                                     password,
403                                     credentials,
404                                     pending_requests);
405
406    // Delay deletion as we're on the stack.
407    MessageLoop::current()->DeleteSoon(FROM_HERE, this);
408  }
409}
410
411void ExistingUserController::OnOffTheRecordLoginSuccess() {
412  if (WizardController::IsDeviceRegistered()) {
413    LoginUtils::Get()->CompleteOffTheRecordLogin(guest_mode_url_);
414  } else {
415    // Postpone CompleteOffTheRecordLogin until registration completion.
416    ActivateWizard(WizardController::kRegistrationScreenName);
417  }
418}
419
420void ExistingUserController::OnPasswordChangeDetected(
421    const GaiaAuthConsumer::ClientLoginResult& credentials) {
422  // Must not proceed without signature verification.
423  bool trusted_setting_available = user_settings_->RequestTrustedOwner(
424      method_factory_.NewRunnableMethod(
425          &ExistingUserController::OnPasswordChangeDetected,
426          credentials));
427  if (!trusted_setting_available) {
428    // Value of owner email is still not verified.
429    // Another attempt will be invoked after verification completion.
430    return;
431  }
432  // TODO(altimofeev): remove this constrain when full sync for the owner will
433  // be correctly handled.
434  bool full_sync_disabled = (UserCrosSettingsProvider::cached_owner() ==
435      last_login_attempt_username_);
436
437  PasswordChangedView* view = new PasswordChangedView(this, full_sync_disabled);
438  views::Window* window = browser::CreateViewsWindow(GetNativeWindow(),
439                                                     gfx::Rect(),
440                                                     view);
441  window->SetIsAlwaysOnTop(true);
442  window->Show();
443}
444
445void ExistingUserController::WhiteListCheckFailed(const std::string& email) {
446  ShowError(IDS_LOGIN_ERROR_WHITELIST, email);
447
448  // Reenable clicking on other windows and status area.
449  login_display_->SetUIEnabled(true);
450  SetStatusAreaEnabled(true);
451}
452
453////////////////////////////////////////////////////////////////////////////////
454// ExistingUserController, CaptchaView::Delegate implementation:
455//
456
457void ExistingUserController::OnCaptchaEntered(const std::string& captcha) {
458  login_performer_->set_captcha(captcha);
459}
460
461////////////////////////////////////////////////////////////////////////////////
462// ExistingUserController, PasswordChangedView::Delegate implementation:
463//
464
465void ExistingUserController::RecoverEncryptedData(
466    const std::string& old_password) {
467  // LoginPerformer instance has state of the user so it should exist.
468  if (login_performer_.get())
469    login_performer_->RecoverEncryptedData(old_password);
470}
471
472void ExistingUserController::ResyncEncryptedData() {
473  // LoginPerformer instance has state of the user so it should exist.
474  if (login_performer_.get())
475    login_performer_->ResyncEncryptedData();
476}
477
478////////////////////////////////////////////////////////////////////////////////
479// ExistingUserController, private:
480
481void ExistingUserController::ActivateWizard(const std::string& screen_name) {
482  // WizardController takes care of deleting itself when done.
483  WizardController* controller = new WizardController();
484
485  // Give the background window to the controller.
486  controller->OwnBackground(background_window_, background_view_);
487  background_window_ = NULL;
488
489  controller->Init(screen_name, background_bounds_);
490  if (chromeos::UserManager::Get()->IsLoggedInAsGuest())
491    controller->set_start_url(guest_mode_url_);
492
493  login_display_->OnFadeOut();
494
495  delete_timer_.Start(base::TimeDelta::FromSeconds(1), this,
496                      &ExistingUserController::Delete);
497}
498
499LoginDisplay* ExistingUserController::CreateLoginDisplay(
500    LoginDisplay::Delegate* delegate, const gfx::Rect& background_bounds) {
501  // TODO(rharrison): Create Web UI implementation too. http://crosbug.com/6398.
502  return new ViewsLoginDisplay(delegate, background_bounds);
503}
504
505void ExistingUserController::Delete() {
506  delete this;
507}
508
509gfx::NativeWindow ExistingUserController::GetNativeWindow() const {
510  return background_view_->GetNativeWindow();
511}
512
513void ExistingUserController::SetStatusAreaEnabled(bool enable) {
514  if (background_view_) {
515    background_view_->SetStatusAreaEnabled(enable);
516  }
517}
518
519void ExistingUserController::ShowError(int error_id,
520                                       const std::string& details) {
521  // TODO(dpolukhin): show detailed error info. |details| string contains
522  // low level error info that is not localized and even is not user friendly.
523  // For now just ignore it because error_text contains all required information
524  // for end users, developers can see details string in Chrome logs.
525  VLOG(1) << details;
526  HelpAppLauncher::HelpTopic help_topic_id;
527  switch (login_performer_->error().state()) {
528    case GoogleServiceAuthError::CONNECTION_FAILED:
529      help_topic_id = HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE;
530      break;
531    case GoogleServiceAuthError::ACCOUNT_DISABLED:
532      help_topic_id = HelpAppLauncher::HELP_ACCOUNT_DISABLED;
533      break;
534    case GoogleServiceAuthError::HOSTED_NOT_ALLOWED:
535      help_topic_id = HelpAppLauncher::HELP_HOSTED_ACCOUNT;
536      break;
537    default:
538      help_topic_id = login_performer_->login_timed_out() ?
539          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE :
540          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT;
541      break;
542  }
543
544  login_display_->ShowError(error_id, num_login_attempts_, help_topic_id);
545}
546
547}  // namespace chromeos
548