普通文本  |  497行  |  18.34 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/login_performer.h"

#include <string>

#include "base/logging.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/utf_string_conversions.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/screen_lock_library.h"
#include "chrome/browser/chromeos/cros_settings_names.h"
#include "chrome/browser/chromeos/login/login_utils.h"
#include "chrome/browser/chromeos/login/screen_locker.h"
#include "chrome/browser/chromeos/user_cros_settings_provider.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "content/browser/browser_thread.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 "ui/base/resource/resource_bundle.h"

namespace chromeos {

// Initialize default LoginPerformer.
// static
LoginPerformer* LoginPerformer::default_performer_ = NULL;

LoginPerformer::LoginPerformer(Delegate* delegate)
    : last_login_failure_(LoginFailure::None()),
      delegate_(delegate),
      password_changed_(false),
      screen_lock_requested_(false),
      initial_online_auth_pending_(false),
      method_factory_(this) {
  DCHECK(default_performer_ == NULL)
      << "LoginPerformer should have only one instance.";
  default_performer_ = this;
}

LoginPerformer::~LoginPerformer() {
  DVLOG(1) << "Deleting LoginPerformer";
  DCHECK(default_performer_ != NULL) << "Default instance should exist.";
  default_performer_ = NULL;
}

////////////////////////////////////////////////////////////////////////////////
// LoginPerformer, LoginStatusConsumer implementation:

void LoginPerformer::OnLoginFailure(const LoginFailure& failure) {
  UserMetrics::RecordAction(UserMetricsAction("Login_Failure"));
  UMA_HISTOGRAM_ENUMERATION("Login.FailureReason", failure.reason(),
                            LoginFailure::NUM_FAILURE_REASONS);

  DVLOG(1) << "failure.reason " << failure.reason();
  DVLOG(1) << "failure.error.state " << failure.error().state();

  last_login_failure_ = failure;
  if (delegate_) {
    captcha_.clear();
    captcha_token_.clear();
    if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED &&
        failure.error().state() == GoogleServiceAuthError::CAPTCHA_REQUIRED) {
      captcha_token_ = failure.error().captcha().token;
    }
    delegate_->OnLoginFailure(failure);
    return;
  }

  // Consequent online login failure with blocking UI on.
  // No difference between cases whether screen was locked by the user or
  // by LoginPerformer except for the very first screen lock while waiting
  // for online auth. Otherwise it will be SL active > timeout > screen unlock.
  // Display recoverable error message using ScreenLocker,
  // force sign out otherwise.
  if (ScreenLocker::default_screen_locker() && !initial_online_auth_pending_) {
    ResolveLockLoginFailure();
    return;
  }
  initial_online_auth_pending_ = false;

  // Offline auth - OK, online auth - failed.
  if (failure.reason() == LoginFailure::NETWORK_AUTH_FAILED) {
    ResolveInitialNetworkAuthFailure();
  } else if (failure.reason() == LoginFailure::LOGIN_TIMED_OUT) {
    VLOG(1) << "Online login timed out. "
            << "Granting user access based on offline auth only.";
    // ScreenLock is not active, it's ok to delete itself.
    MessageLoop::current()->DeleteSoon(FROM_HERE, this);
  } else {
    // COULD_NOT_MOUNT_CRYPTOHOME, COULD_NOT_MOUNT_TMPFS:
    // happens during offline auth only.
    // UNLOCK_FAILED is used during normal screen lock case.
    // TODO(nkostylev) DATA_REMOVAL_FAILED - ?
    NOTREACHED();
  }
}

void LoginPerformer::OnLoginSuccess(
    const std::string& username,
    const std::string& password,
    const GaiaAuthConsumer::ClientLoginResult& credentials,
    bool pending_requests) {
  UserMetrics::RecordAction(UserMetricsAction("Login_Success"));
  // 0 - Login success offline and online. It's a new user. or it's an
  //     existing user and offline auth took longer than online auth.
  // 1 - Login success offline only. It's an existing user login.
  UMA_HISTOGRAM_ENUMERATION("Login.SuccessReason", pending_requests, 2);

  VLOG(1) << "LoginSuccess, pending_requests " << pending_requests;
  if (delegate_) {
    // After delegate_->OnLoginSuccess(...) is called, delegate_ releases
    // LoginPerformer ownership. LP now manages it's lifetime on its own.
    // 2 things could make it exist longer:
    // 1. ScreenLock active (pending correct new password input)
    // 2. Pending online auth request.
    if (!pending_requests)
      MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    else
      initial_online_auth_pending_ = true;

    delegate_->OnLoginSuccess(username,
                              password,
                              credentials,
                              pending_requests);
    return;
  } else {
    // Online login has succeeded.
    DCHECK(!pending_requests)
        << "Pending request w/o delegate_ should not happen!";
    // It is not guaranted, that profile creation has been finished yet. So use
    // async version here.
    credentials_ = credentials;
    ProfileManager::CreateDefaultProfileAsync(this);
  }
}

void LoginPerformer::OnProfileCreated(Profile* profile) {
  CHECK(profile);

  LoginUtils::Get()->FetchCookies(profile, credentials_);
  LoginUtils::Get()->FetchTokens(profile, credentials_);
  credentials_ = GaiaAuthConsumer::ClientLoginResult();

  // Don't unlock screen if it was locked while we're waiting
  // for initial online auth.
  if (ScreenLocker::default_screen_locker() &&
      !initial_online_auth_pending_) {
    DVLOG(1) << "Online login OK - unlocking screen.";
    RequestScreenUnlock();
    // Do not delete itself just yet, wait for unlock.
    // See ResolveScreenUnlocked().
    return;
  }
  initial_online_auth_pending_ = false;
  // There's nothing else that's holding LP from deleting itself -
  // no ScreenLock, no pending requests.
  MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}

void LoginPerformer::OnOffTheRecordLoginSuccess() {
  UserMetrics::RecordAction(
      UserMetricsAction("Login_GuestLoginSuccess"));

  if (delegate_)
    delegate_->OnOffTheRecordLoginSuccess();
  else
    NOTREACHED();
}

void LoginPerformer::OnPasswordChangeDetected(
    const GaiaAuthConsumer::ClientLoginResult& credentials) {
  cached_credentials_ = credentials;
  if (delegate_) {
    delegate_->OnPasswordChangeDetected(credentials);
  } else {
    last_login_failure_ =
        LoginFailure::FromNetworkAuthFailure(GoogleServiceAuthError(
            GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
    password_changed_ = true;
    DVLOG(1) << "Password change detected - locking screen.";
    RequestScreenLock();
  }
}

////////////////////////////////////////////////////////////////////////////////
// LoginPerformer, SignedSettingsHelper::Callback implementation:

void LoginPerformer::OnCheckWhitelistCompleted(SignedSettings::ReturnCode code,
                                               const std::string& email) {
  if (code == SignedSettings::SUCCESS) {
    // Whitelist check passed, continue with authentication.
    StartAuthentication();
  } else {
    if (delegate_)
      delegate_->WhiteListCheckFailed(email);
    else
      NOTREACHED();
  }
}

////////////////////////////////////////////////////////////////////////////////
// LoginPerformer, NotificationObserver implementation:
//

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

  bool is_screen_locked = *Details<bool>(details).ptr();
  if (is_screen_locked) {
    if (screen_lock_requested_) {
      screen_lock_requested_ = false;
      ResolveScreenLocked();
    }
  } else {
    ResolveScreenUnlocked();
  }
}

////////////////////////////////////////////////////////////////////////////////
// LoginPerformer, public:

void LoginPerformer::Login(const std::string& username,
                           const std::string& password) {
  username_ = username;
  password_ = password;

  // Whitelist check is always performed during initial login and
  // should not be performed when ScreenLock is active (pending online auth).
  if (!ScreenLocker::default_screen_locker()) {
    // Must not proceed without signature verification.
    UserCrosSettingsProvider user_settings;
    bool trusted_setting_available = user_settings.RequestTrustedAllowNewUser(
        method_factory_.NewRunnableMethod(&LoginPerformer::Login,
                                          username,
                                          password));
    if (!trusted_setting_available) {
      // Value of AllowNewUser setting is still not verified.
      // Another attempt will be invoked after verification completion.
      return;
    }
  }

  if (ScreenLocker::default_screen_locker() ||
      UserCrosSettingsProvider::cached_allow_new_user()) {
    // Starts authentication if guest login is allowed or online auth pending.
    StartAuthentication();
  } else {
    // Otherwise, do whitelist check first.
    PrefService* local_state = g_browser_process->local_state();
    CHECK(local_state);
    if (local_state->IsManagedPreference(kAccountsPrefUsers)) {
      if (UserCrosSettingsProvider::IsEmailInCachedWhitelist(username)) {
        StartAuthentication();
      } else {
        if (delegate_)
          delegate_->WhiteListCheckFailed(username);
        else
          NOTREACHED();
      }
    } else {
      // In case of signed settings: with current implementation we do not
      // trust whitelist returned by PrefService.  So make separate check.
      SignedSettingsHelper::Get()->StartCheckWhitelistOp(
          username, this);
    }
  }
}

void LoginPerformer::LoginOffTheRecord() {
  authenticator_ = LoginUtils::Get()->CreateAuthenticator(this);
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(authenticator_.get(),
                        &Authenticator::LoginOffTheRecord));
}

void LoginPerformer::RecoverEncryptedData(const std::string& old_password) {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(authenticator_.get(),
                        &Authenticator::RecoverEncryptedData,
                        old_password,
                        cached_credentials_));
  cached_credentials_ = GaiaAuthConsumer::ClientLoginResult();
}

void LoginPerformer::ResyncEncryptedData() {
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(authenticator_.get(),
                        &Authenticator::ResyncEncryptedData,
                        cached_credentials_));
  cached_credentials_ = GaiaAuthConsumer::ClientLoginResult();
}

////////////////////////////////////////////////////////////////////////////////
// LoginPerformer, private:

void LoginPerformer::RequestScreenLock() {
  DVLOG(1) << "Screen lock requested";
  // Will receive notifications on screen unlock and delete itself.
  registrar_.Add(this,
                 NotificationType::SCREEN_LOCK_STATE_CHANGED,
                 NotificationService::AllSources());
  if (ScreenLocker::default_screen_locker()) {
    DVLOG(1) << "Screen already locked";
    ResolveScreenLocked();
  } else {
    screen_lock_requested_ = true;
    chromeos::CrosLibrary::Get()->GetScreenLockLibrary()->
        NotifyScreenLockRequested();
  }
}

void LoginPerformer::RequestScreenUnlock() {
  DVLOG(1) << "Screen unlock requested";
  if (ScreenLocker::default_screen_locker()) {
    chromeos::CrosLibrary::Get()->GetScreenLockLibrary()->
        NotifyScreenUnlockRequested();
    // Will unsubscribe from notifications once unlock is successful.
  } else {
    LOG(ERROR) << "Screen is not locked";
    NOTREACHED();
  }
}

void LoginPerformer::ResolveInitialNetworkAuthFailure() {
  DVLOG(1) << "auth_error: " << last_login_failure_.error().state();

  switch (last_login_failure_.error().state()) {
    case GoogleServiceAuthError::CONNECTION_FAILED:
    case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
    case GoogleServiceAuthError::TWO_FACTOR:
    case GoogleServiceAuthError::REQUEST_CANCELED:
      // Offline auth already done. Online auth will be done next time
      // or once user accesses web property.
      VLOG(1) << "Granting user access based on offline auth only. "
              << "Online login failed with "
              << last_login_failure_.error().state();
      // Resolving initial online auth failure, no ScreenLock is active.
      MessageLoop::current()->DeleteSoon(FROM_HERE, this);
      return;
    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
      // Offline auth OK, so it might be the case of changed password.
      password_changed_ = true;
    case GoogleServiceAuthError::USER_NOT_SIGNED_UP:
    case GoogleServiceAuthError::ACCOUNT_DELETED:
    case GoogleServiceAuthError::ACCOUNT_DISABLED:
      // Access not granted. User has to sign out.
      // Request screen lock & show error message there.
    case GoogleServiceAuthError::CAPTCHA_REQUIRED:
      // User is requested to enter CAPTCHA challenge.
      captcha_token_ = last_login_failure_.error().captcha().token;
      RequestScreenLock();
      return;
    default:
      // Unless there's new GoogleServiceAuthErrors state has been added.
      NOTREACHED();
      return;
  }
}

void LoginPerformer::ResolveLockLoginFailure() {
  if (last_login_failure_.reason() == LoginFailure::LOGIN_TIMED_OUT) {
     LOG(WARNING) << "Online login timed out - unlocking screen. "
                  << "Granting user access based on offline auth only.";
     RequestScreenUnlock();
     return;
   } else if (last_login_failure_.reason() ==
                  LoginFailure::NETWORK_AUTH_FAILED) {
     ResolveLockNetworkAuthFailure();
     return;
   } else if (last_login_failure_.reason() ==
                  LoginFailure::COULD_NOT_MOUNT_CRYPTOHOME ||
              last_login_failure_.reason() ==
                  LoginFailure::DATA_REMOVAL_FAILED) {
     LOG(ERROR) << "Cryptohome error, forcing sign out.";
   } else {
     // COULD_NOT_MOUNT_TMPFS, UNLOCK_FAILED should not happen here.
     NOTREACHED();
   }
   ScreenLocker::default_screen_locker()->Signout();
}

void LoginPerformer::ResolveLockNetworkAuthFailure() {
  DCHECK(ScreenLocker::default_screen_locker())
      << "ScreenLocker instance doesn't exist.";
  DCHECK(last_login_failure_.reason() == LoginFailure::NETWORK_AUTH_FAILED);

  string16 msg;
  bool sign_out_only = false;

  DVLOG(1) << "auth_error: " << last_login_failure_.error().state();

  switch (last_login_failure_.error().state()) {
    case GoogleServiceAuthError::CONNECTION_FAILED:
    case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
    case GoogleServiceAuthError::TWO_FACTOR:
    case GoogleServiceAuthError::REQUEST_CANCELED:
      // Offline auth already done. Online auth will be done next time
      // or once user accesses web property.
      LOG(WARNING) << "Granting user access based on offline auth only. "
                   << "Online login failed with "
                   << last_login_failure_.error().state();
      RequestScreenUnlock();
      return;
    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
      // Password change detected.
      msg = l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_PASSWORD_CHANGED);
      break;
    case GoogleServiceAuthError::USER_NOT_SIGNED_UP:
    case GoogleServiceAuthError::ACCOUNT_DELETED:
    case GoogleServiceAuthError::ACCOUNT_DISABLED:
      // Access not granted. User has to sign out.
      // Show error message using existing screen lock.
      msg = l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_RESTRICTED);
      sign_out_only = true;
      break;
    case GoogleServiceAuthError::CAPTCHA_REQUIRED:
      // User is requested to enter CAPTCHA challenge.
      captcha_token_ = last_login_failure_.error().captcha().token;
      msg = l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_PASSWORD_CHANGED);
      ScreenLocker::default_screen_locker()->ShowCaptchaAndErrorMessage(
          last_login_failure_.error().captcha().image_url,
          UTF16ToWide(msg));
      return;
    default:
      // Unless there's new GoogleServiceAuthError state has been added.
      NOTREACHED();
      break;
  }

  ScreenLocker::default_screen_locker()->ShowErrorMessage(UTF16ToWide(msg),
                                                          sign_out_only);
}

void LoginPerformer::ResolveScreenLocked() {
  DVLOG(1) << "Screen locked";
  ResolveLockNetworkAuthFailure();
}

void LoginPerformer::ResolveScreenUnlocked() {
  DVLOG(1) << "Screen unlocked";
  registrar_.RemoveAll();
  // If screen was unlocked that was for a reason, should delete itself now.
  MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}

void LoginPerformer::StartAuthentication() {
  DVLOG(1) << "Auth started";
  BootTimesLoader::Get()->AddLoginTimeMarker("AuthStarted", false);
  Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile();
  if (delegate_) {
    authenticator_ = LoginUtils::Get()->CreateAuthenticator(this);
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(authenticator_.get(),
                          &Authenticator::AuthenticateToLogin,
                          profile,
                          username_,
                          password_,
                          captcha_token_,
                          captcha_));
  } else {
    DCHECK(authenticator_.get())
        << "Authenticator instance doesn't exist for login attempt retry.";
    // At this point offline auth has been successful,
    // retry online auth, using existing Authenticator instance.
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(authenticator_.get(),
                          &Authenticator::RetryAuth,
                          profile,
                          username_,
                          password_,
                          captcha_token_,
                          captcha_));
  }
  password_.clear();
}

}  // namespace chromeos