Home | History | Annotate | Download | only in login
      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 
     40 namespace chromeos {
     41 
     42 namespace {
     43 
     44 // Url for setting up sync authentication.
     45 const char kSettingsSyncLoginURL[] = "chrome://settings/personal";
     46 
     47 // URL that will be opened on when user logs in first time on the device.
     48 const char kGetStartedURLPattern[] =
     49     "http://www.gstatic.com/chromebook/gettingstarted/index-%s.html";
     50 
     51 // URL for account creation.
     52 const char kCreateAccountURL[] =
     53     "https://www.google.com/accounts/NewAccount?service=mail";
     54 
     55 // Landing URL when launching Guest mode to fix captive portal.
     56 const char kCaptivePortalLaunchURL[] = "http://www.google.com/";
     57 
     58 }  // namespace
     59 
     60 // static
     61 ExistingUserController* ExistingUserController::current_controller_ = NULL;
     62 
     63 ////////////////////////////////////////////////////////////////////////////////
     64 // ExistingUserController, public:
     65 
     66 ExistingUserController::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 
     81 void 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 
    113 void 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 
    126 ExistingUserController::~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 
    141 void ExistingUserController::CreateAccount() {
    142   guest_mode_url_ =
    143       google_util::AppendGoogleLocaleParam(GURL(kCreateAccountURL));
    144   LoginAsGuest();
    145 }
    146 
    147 string16 ExistingUserController::GetConnectedNetworkName() {
    148   return GetCurrentNetworkName(CrosLibrary::Get()->GetNetworkLibrary());
    149 }
    150 
    151 void ExistingUserController::FixCaptivePortal() {
    152   guest_mode_url_ = GURL(kCaptivePortalLaunchURL);
    153   LoginAsGuest();
    154 }
    155 
    156 void 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 
    188 void 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 
    217 void ExistingUserController::OnUserSelected(const std::string& username) {
    218   login_performer_.reset(NULL);
    219   num_login_attempts_ = 0;
    220 }
    221 
    222 void 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 
    230 void 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 
    244 void 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 
    304 void 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 
    335 void 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 
    375 void 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 
    384 void 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 
    410 void 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 
    422 void ExistingUserController::OnCaptchaEntered(const std::string& captcha) {
    423   login_performer_->set_captcha(captcha);
    424 }
    425 
    426 ////////////////////////////////////////////////////////////////////////////////
    427 // ExistingUserController, PasswordChangedView::Delegate implementation:
    428 //
    429 
    430 void 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 
    437 void 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 
    446 void 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 
    453 gfx::NativeWindow ExistingUserController::GetNativeWindow() const {
    454   return host_->GetNativeWindow();
    455 }
    456 
    457 void ExistingUserController::SetStatusAreaEnabled(bool enable) {
    458   host_->SetStatusAreaEnabled(enable);
    459 }
    460 
    461 void 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