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