普通文本  |  490行  |  18.5 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/login/existing_user_controller.h"

#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/stringprintf.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/cryptohome_library.h"
#include "chrome/browser/chromeos/cros/login_library.h"
#include "chrome/browser/chromeos/cros/network_library.h"
#include "chrome/browser/chromeos/customization_document.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/login_display_host.h"
#include "chrome/browser/chromeos/login/views_login_display.h"
#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/status/status_area_view.h"
#include "chrome/browser/chromeos/user_cros_settings_provider.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/window.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/google_service_auth_error.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "views/window/window.h"

namespace chromeos {

namespace {

// Url for setting up sync authentication.
const char kSettingsSyncLoginURL[] = "chrome://settings/personal";

// URL that will be opened on when user logs in first time on the device.
const char kGetStartedURLPattern[] =
    "http://www.gstatic.com/chromebook/gettingstarted/index-%s.html";

// URL for account creation.
const char kCreateAccountURL[] =
    "https://www.google.com/accounts/NewAccount?service=mail";

// Landing URL when launching Guest mode to fix captive portal.
const char kCaptivePortalLaunchURL[] = "http://www.google.com/";

}  // namespace

// static
ExistingUserController* ExistingUserController::current_controller_ = NULL;

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, public:

ExistingUserController::ExistingUserController(LoginDisplayHost* host)
    : host_(host),
      num_login_attempts_(0),
      user_settings_(new UserCrosSettingsProvider),
      method_factory_(this) {
  DCHECK(current_controller_ == NULL);
  current_controller_ = this;

  login_display_ = host_->CreateLoginDisplay(this);

  registrar_.Add(this,
                 NotificationType::LOGIN_USER_IMAGE_CHANGED,
                 NotificationService::AllSources());
}

void ExistingUserController::Init(const UserVector& users) {
  UserVector filtered_users;
  if (UserCrosSettingsProvider::cached_show_users_on_signin()) {
    for (size_t i = 0; i < users.size(); ++i)
      // TODO(xiyuan): Clean user profile whose email is not in whitelist.
      if (UserCrosSettingsProvider::cached_allow_new_user() ||
          UserCrosSettingsProvider::IsEmailInCachedWhitelist(
              users[i].email())) {
        filtered_users.push_back(users[i]);
      }
  }

  // If no user pods are visible, fallback to single new user pod which will
  // have guest session link.
  bool show_guest = UserCrosSettingsProvider::cached_allow_guest() &&
                    !filtered_users.empty();
  bool show_new_user = true;
  login_display_->set_parent_window(GetNativeWindow());
  login_display_->Init(filtered_users, show_guest, show_new_user);

  LoginUtils::Get()->PrewarmAuthentication();
  if (CrosLibrary::Get()->EnsureLoaded()) {
    CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady();
    CrosLibrary::Get()->GetCryptohomeLibrary()->
        AsyncDoAutomaticFreeDiskSpaceControl(NULL);
  }
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, NotificationObserver implementation:
//

void ExistingUserController::Observe(NotificationType type,
                                     const NotificationSource& source,
                                     const NotificationDetails& details) {
  if (type != NotificationType::LOGIN_USER_IMAGE_CHANGED)
    return;

  UserManager::User* user = Details<UserManager::User>(details).ptr();
  login_display_->OnUserImageChanged(user);
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, private:

ExistingUserController::~ExistingUserController() {
  if (current_controller_ == this) {
    current_controller_ = NULL;
  } else {
    NOTREACHED() << "More than one controller are alive.";
  }
  DCHECK(login_display_ != NULL);
  login_display_->Destroy();
  login_display_ = NULL;
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, LoginDisplay::Delegate implementation:
//

void ExistingUserController::CreateAccount() {
  guest_mode_url_ =
      google_util::AppendGoogleLocaleParam(GURL(kCreateAccountURL));
  LoginAsGuest();
}

string16 ExistingUserController::GetConnectedNetworkName() {
  return GetCurrentNetworkName(CrosLibrary::Get()->GetNetworkLibrary());
}

void ExistingUserController::FixCaptivePortal() {
  guest_mode_url_ = GURL(kCaptivePortalLaunchURL);
  LoginAsGuest();
}

void ExistingUserController::Login(const std::string& username,
                                   const std::string& password) {
  if (username.empty() || password.empty())
    return;
  SetStatusAreaEnabled(false);
  // Disable clicking on other windows.
  login_display_->SetUIEnabled(false);

  BootTimesLoader::Get()->RecordLoginAttempted();

  if (last_login_attempt_username_ != username) {
    last_login_attempt_username_ = username;
    num_login_attempts_ = 0;
  }
  num_login_attempts_++;

  // Use the same LoginPerformer for subsequent login as it has state
  // such as CAPTCHA challenge token & corresponding user input.
  if (!login_performer_.get() || num_login_attempts_ <= 1) {
    LoginPerformer::Delegate* delegate = this;
    if (login_performer_delegate_.get())
      delegate = login_performer_delegate_.get();
    // Only one instance of LoginPerformer should exist at a time.
    login_performer_.reset(NULL);
    login_performer_.reset(new LoginPerformer(delegate));
  }
  login_performer_->Login(username, password);
  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
      l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNING_IN).c_str(),
      false, true);
}

void ExistingUserController::LoginAsGuest() {
  SetStatusAreaEnabled(false);
  // Disable clicking on other windows.
  login_display_->SetUIEnabled(false);

  // Check allow_guest in case this call is fired from key accelerator.
  // Must not proceed without signature verification.
  bool trusted_setting_available = user_settings_->RequestTrustedAllowGuest(
      method_factory_.NewRunnableMethod(
          &ExistingUserController::LoginAsGuest));
  if (!trusted_setting_available) {
    // Value of AllowGuest setting is still not verified.
    // Another attempt will be invoked again after verification completion.
    return;
  }
  if (!UserCrosSettingsProvider::cached_allow_guest()) {
    // Disallowed.
    return;
  }

  // Only one instance of LoginPerformer should exist at a time.
  login_performer_.reset(NULL);
  login_performer_.reset(new LoginPerformer(this));
  login_performer_->LoginOffTheRecord();
  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
      l10n_util::GetStringUTF8(IDS_CHROMEOS_ACC_LOGIN_SIGNIN_OFFRECORD).c_str(),
      false, true);
}

void ExistingUserController::OnUserSelected(const std::string& username) {
  login_performer_.reset(NULL);
  num_login_attempts_ = 0;
}

void ExistingUserController::OnStartEnterpriseEnrollment() {
  CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kEnableDevicePolicy)) {
    ownership_checker_.reset(new OwnershipStatusChecker(NewCallback(
        this, &ExistingUserController::OnEnrollmentOwnershipCheckCompleted)));
  }
}

void ExistingUserController::OnEnrollmentOwnershipCheckCompleted(
    OwnershipService::Status status) {
  if (status == OwnershipService::OWNERSHIP_NONE) {
    host_->StartWizard(WizardController::kEnterpriseEnrollmentScreenName,
                       GURL());
    login_display_->OnFadeOut();
  }
  ownership_checker_.reset();
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, LoginPerformer::Delegate implementation:
//

void ExistingUserController::OnLoginFailure(const LoginFailure& failure) {
  guest_mode_url_ = GURL::EmptyGURL();
  std::string error = failure.GetErrorString();

  // Check networking after trying to login in case user is
  // cached locally or the local admin account.
  bool is_known_user =
      UserManager::Get()->IsKnownUser(last_login_attempt_username_);
  NetworkLibrary* network = CrosLibrary::Get()->GetNetworkLibrary();
  if (!network || !CrosLibrary::Get()->EnsureLoaded()) {
    ShowError(IDS_LOGIN_ERROR_NO_NETWORK_LIBRARY, error);
  } else if (!network->Connected()) {
    if (is_known_user)
      ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
    else
      ShowError(IDS_LOGIN_ERROR_OFFLINE_FAILED_NETWORK_NOT_CONNECTED, error);
  } else {
    if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
        failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) {
      if (!failure.error().captcha().image_url.is_empty()) {
        CaptchaView* view =
            new CaptchaView(failure.error().captcha().image_url, false);
        view->Init();
        view->set_delegate(this);
        views::Window* window = browser::CreateViewsWindow(
            GetNativeWindow(), gfx::Rect(), view);
        window->SetIsAlwaysOnTop(true);
        window->Show();
      } else {
        LOG(WARNING) << "No captcha image url was found?";
        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
      }
    } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
               failure.error().state() ==
                   GoogleServiceAuthError::HOSTED_NOT_ALLOWED) {
      ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED, error);
    } else if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
               failure.error().state() ==
                   GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
      // SERVICE_UNAVAILABLE is generated in 2 cases:
      // 1. ClientLogin returns ServiceUnavailable code.
      // 2. Internet connectivity may be behind the captive portal.
      // Suggesting user to try sign in to a portal in Guest mode.
      if (UserCrosSettingsProvider::cached_allow_guest())
        ShowError(IDS_LOGIN_ERROR_CAPTIVE_PORTAL, error);
      else
        ShowError(IDS_LOGIN_ERROR_CAPTIVE_PORTAL_NO_GUEST_MODE, error);
    } else {
      if (!is_known_user)
        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING_NEW, error);
      else
        ShowError(IDS_LOGIN_ERROR_AUTHENTICATING, error);
    }
  }

  // Reenable clicking on other windows and status area.
  login_display_->SetUIEnabled(true);
  SetStatusAreaEnabled(true);
}

void ExistingUserController::OnLoginSuccess(
    const std::string& username,
    const std::string& password,
    const GaiaAuthConsumer::ClientLoginResult& credentials,
    bool pending_requests) {
  bool known_user = UserManager::Get()->IsKnownUser(username);
  bool login_only =
      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kLoginScreen) == WizardController::kLoginScreenName;
  ready_for_browser_launch_ = known_user || login_only;

  two_factor_credentials_ = credentials.two_factor;

  // LoginPerformer instance will delete itself once online auth result is OK.
  // In case of failure it'll bring up ScreenLock and ask for
  // correct password/display error message.
  // Even in case when following online,offline protocol and returning
  // requests_pending = false, let LoginPerformer delete itself.
  login_performer_->set_delegate(NULL);
  LoginPerformer* performer = login_performer_.release();
  performer = NULL;

  // Will call OnProfilePrepared() in the end.
  LoginUtils::Get()->PrepareProfile(username,
                                    password,
                                    credentials,
                                    pending_requests,
                                    this);

}

void ExistingUserController::OnProfilePrepared(Profile* profile) {
  // TODO(nkostylev): May add login UI implementation callback call.
  if (!ready_for_browser_launch_) {
    PrefService* prefs = g_browser_process->local_state();
    const std::string current_locale =
        StringToLowerASCII(prefs->GetString(prefs::kApplicationLocale));
    std::string start_url =
      base::StringPrintf(kGetStartedURLPattern, current_locale.c_str());
    CommandLine::ForCurrentProcess()->AppendArg(start_url);

    ServicesCustomizationDocument* customization =
      ServicesCustomizationDocument::GetInstance();
    if (!ServicesCustomizationDocument::WasApplied() &&
        customization->IsReady()) {
      std::string locale = g_browser_process->GetApplicationLocale();
      std::string initial_start_page =
          customization->GetInitialStartPage(locale);
      if (!initial_start_page.empty())
        CommandLine::ForCurrentProcess()->AppendArg(initial_start_page);
      customization->ApplyCustomization();
    }

    if (two_factor_credentials_) {
      // If we have a two factor error and and this is a new user,
      // load the personal settings page.
      // TODO(stevenjb): direct the user to a lightweight sync login page.
      CommandLine::ForCurrentProcess()->AppendArg(kSettingsSyncLoginURL);
    }

    ActivateWizard(WizardController::IsDeviceRegistered() ?
        WizardController::kUserImageScreenName :
        WizardController::kRegistrationScreenName);
  } else {
    LoginUtils::DoBrowserLaunch(profile);
    // Delay deletion as we're on the stack.
    host_->OnSessionStart();
  }
  login_display_->OnFadeOut();
}

void ExistingUserController::OnOffTheRecordLoginSuccess() {
  if (WizardController::IsDeviceRegistered()) {
    LoginUtils::Get()->CompleteOffTheRecordLogin(guest_mode_url_);
  } else {
    // Postpone CompleteOffTheRecordLogin until registration completion.
    ActivateWizard(WizardController::kRegistrationScreenName);
  }
}

void ExistingUserController::OnPasswordChangeDetected(
    const GaiaAuthConsumer::ClientLoginResult& credentials) {
  // Must not proceed without signature verification.
  bool trusted_setting_available = user_settings_->RequestTrustedOwner(
      method_factory_.NewRunnableMethod(
          &ExistingUserController::OnPasswordChangeDetected,
          credentials));
  if (!trusted_setting_available) {
    // Value of owner email is still not verified.
    // Another attempt will be invoked after verification completion.
    return;
  }

  // Passing 'false' here enables "full sync" mode in the dialog,
  // which disables the requirement for the old owner password,
  // allowing us to recover from a lost owner password/homedir.
  // TODO(gspencer): We shouldn't have to erase stateful data when
  // doing this.  See http://crosbug.com/9115 http://crosbug.com/7792
  PasswordChangedView* view = new PasswordChangedView(this, false);
  views::Window* window = browser::CreateViewsWindow(GetNativeWindow(),
                                                     gfx::Rect(),
                                                     view);
  window->SetIsAlwaysOnTop(true);
  window->Show();
}

void ExistingUserController::WhiteListCheckFailed(const std::string& email) {
  ShowError(IDS_LOGIN_ERROR_WHITELIST, email);

  // Reenable clicking on other windows and status area.
  login_display_->SetUIEnabled(true);
  SetStatusAreaEnabled(true);
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, CaptchaView::Delegate implementation:
//

void ExistingUserController::OnCaptchaEntered(const std::string& captcha) {
  login_performer_->set_captcha(captcha);
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, PasswordChangedView::Delegate implementation:
//

void ExistingUserController::RecoverEncryptedData(
    const std::string& old_password) {
  // LoginPerformer instance has state of the user so it should exist.
  if (login_performer_.get())
    login_performer_->RecoverEncryptedData(old_password);
}

void ExistingUserController::ResyncEncryptedData() {
  // LoginPerformer instance has state of the user so it should exist.
  if (login_performer_.get())
    login_performer_->ResyncEncryptedData();
}

////////////////////////////////////////////////////////////////////////////////
// ExistingUserController, private:

void ExistingUserController::ActivateWizard(const std::string& screen_name) {
  GURL start_url;
  if (chromeos::UserManager::Get()->IsLoggedInAsGuest())
    start_url = guest_mode_url_;
  host_->StartWizard(screen_name, start_url);
}

gfx::NativeWindow ExistingUserController::GetNativeWindow() const {
  return host_->GetNativeWindow();
}

void ExistingUserController::SetStatusAreaEnabled(bool enable) {
  host_->SetStatusAreaEnabled(enable);
}

void ExistingUserController::ShowError(int error_id,
                                       const std::string& details) {
  // TODO(dpolukhin): show detailed error info. |details| string contains
  // low level error info that is not localized and even is not user friendly.
  // For now just ignore it because error_text contains all required information
  // for end users, developers can see details string in Chrome logs.
  VLOG(1) << details;
  HelpAppLauncher::HelpTopic help_topic_id;
  switch (login_performer_->error().state()) {
    case GoogleServiceAuthError::CONNECTION_FAILED:
      help_topic_id = HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE;
      break;
    case GoogleServiceAuthError::ACCOUNT_DISABLED:
      help_topic_id = HelpAppLauncher::HELP_ACCOUNT_DISABLED;
      break;
    case GoogleServiceAuthError::HOSTED_NOT_ALLOWED:
      help_topic_id = HelpAppLauncher::HELP_HOSTED_ACCOUNT;
      break;
    default:
      help_topic_id = login_performer_->login_timed_out() ?
          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT_OFFLINE :
          HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT;
      break;
  }

  login_display_->ShowError(error_id, num_login_attempts_, help_topic_id);
}

}  // namespace chromeos