root/chrome/browser/chromeos/login/parallel_authenticator.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. TriggerResolve
  2. TriggerResolveHash
  3. TriggerResolveWithLoginTimeMarker
  4. Mount
  5. MountGuest
  6. MountGuestAndGetHash
  7. MountPublic
  8. Migrate
  9. Remove
  10. CheckKey
  11. delayed_login_failure_
  12. AuthenticateToLogin
  13. CompleteLogin
  14. AuthenticateToUnlock
  15. LoginAsLocallyManagedUser
  16. LoginRetailMode
  17. LoginOffTheRecord
  18. LoginAsPublicAccount
  19. LoginAsKioskAccount
  20. OnRetailModeLoginSuccess
  21. OnLoginSuccess
  22. OnOffTheRecordLoginSuccess
  23. OnPasswordChangeDetected
  24. OnLoginFailure
  25. RecoverEncryptedData
  26. RemoveEncryptedData
  27. ResyncEncryptedData
  28. VerifyOwner
  29. OnOwnershipChecked
  30. Resolve
  31. HashPassword
  32. ResolveState
  33. ResolveCryptohomeFailureState
  34. ResolveCryptohomeSuccessState
  35. ResolveOnlineSuccessState
  36. ResolveLoginCompletionStatus
  37. SetOwnerState

// Copyright (c) 2012 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/parallel_authenticator.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
#include "chrome/browser/chromeos/login/authentication_notification_details.h"
#include "chrome/browser/chromeos/login/login_status_consumer.h"
#include "chrome/browser/chromeos/login/user.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/system_salt_getter.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/login/login_state.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "crypto/sha2.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using content::BrowserThread;

namespace chromeos {

namespace {

// Length of password hashed with SHA-256.
const int kPasswordHashLength = 32;

// Records status and calls resolver->Resolve().
void TriggerResolve(AuthAttemptState* attempt,
                    scoped_refptr<ParallelAuthenticator> resolver,
                    bool success,
                    cryptohome::MountError return_code) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  attempt->RecordCryptohomeStatus(success, return_code);
  resolver->Resolve();
}

// Records get hash status and calls resolver->Resolve().
void TriggerResolveHash(AuthAttemptState* attempt,
                        scoped_refptr<ParallelAuthenticator> resolver,
                        bool success,
                        const std::string& username_hash) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (success)
    attempt->RecordUsernameHash(username_hash);
  else
    attempt->RecordUsernameHashFailed();
  resolver->Resolve();
}

// Calls TriggerResolve while adding login time marker.
void TriggerResolveWithLoginTimeMarker(
    const std::string& marker_name,
    AuthAttemptState* attempt,
    scoped_refptr<ParallelAuthenticator> resolver,
    bool success,
    cryptohome::MountError return_code) {
  chromeos::BootTimesLoader::Get()->AddLoginTimeMarker(marker_name, false);
  TriggerResolve(attempt, resolver, success, return_code);
}

// Calls cryptohome's mount method.
void Mount(AuthAttemptState* attempt,
           scoped_refptr<ParallelAuthenticator> resolver,
           int flags,
           const std::string& system_salt) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  chromeos::BootTimesLoader::Get()->AddLoginTimeMarker(
      "CryptohomeMount-Start", false);
  // Set state that username_hash is requested here so that test implementation
  // that returns directly would not generate 2 OnLoginSucces() calls.
  attempt->UsernameHashRequested();
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncMount(
      attempt->user_context.username,
      ParallelAuthenticator::HashPassword(attempt->user_context.password,
                                          system_salt),
      flags,
      base::Bind(&TriggerResolveWithLoginTimeMarker,
                 "CryptohomeMount-End",
                 attempt,
                 resolver));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername(
      attempt->user_context.username,
      base::Bind(&TriggerResolveHash,
                 attempt,
                 resolver));
}

// Calls cryptohome's mount method for guest.
void MountGuest(AuthAttemptState* attempt,
                scoped_refptr<ParallelAuthenticator> resolver) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountGuest(
      base::Bind(&TriggerResolveWithLoginTimeMarker,
                 "CryptohomeMount-End",
                 attempt,
                 resolver));
}

// Calls cryptohome's mount method for guest and also get the user hash from
// cryptohome.
void MountGuestAndGetHash(AuthAttemptState* attempt,
                          scoped_refptr<ParallelAuthenticator> resolver) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  attempt->UsernameHashRequested();
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountGuest(
      base::Bind(&TriggerResolveWithLoginTimeMarker,
                 "CryptohomeMount-End",
                 attempt,
                 resolver));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername(
      attempt->user_context.username,
      base::Bind(&TriggerResolveHash,
                 attempt,
                 resolver));
}

// Calls cryptohome's MountPublic method
void MountPublic(AuthAttemptState* attempt,
                 scoped_refptr<ParallelAuthenticator> resolver,
                 int flags) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountPublic(
      attempt->user_context.username,
      flags,
      base::Bind(&TriggerResolveWithLoginTimeMarker,
                 "CryptohomeMountPublic-End",
                 attempt,
                 resolver));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername(
      attempt->user_context.username,
      base::Bind(&TriggerResolveHash,
                 attempt,
                 resolver));
}

// Calls cryptohome's key migration method.
void Migrate(AuthAttemptState* attempt,
             scoped_refptr<ParallelAuthenticator> resolver,
             bool passing_old_hash,
             const std::string& old_password,
             const std::string& system_salt) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  chromeos::BootTimesLoader::Get()->AddLoginTimeMarker(
      "CryptohomeMigrate-Start", false);
  cryptohome::AsyncMethodCaller* caller =
      cryptohome::AsyncMethodCaller::GetInstance();
  if (passing_old_hash) {
    caller->AsyncMigrateKey(
        attempt->user_context.username,
        ParallelAuthenticator::HashPassword(old_password, system_salt),
        ParallelAuthenticator::HashPassword(attempt->user_context.password,
                                            system_salt),
        base::Bind(&TriggerResolveWithLoginTimeMarker,
                   "CryptohomeMount-End",
                   attempt,
                   resolver));
  } else {
    caller->AsyncMigrateKey(
        attempt->user_context.username,
        ParallelAuthenticator::HashPassword(attempt->user_context.password,
                                            system_salt),
        ParallelAuthenticator::HashPassword(old_password, system_salt),
        base::Bind(&TriggerResolveWithLoginTimeMarker,
                   "CryptohomeMount-End",
                   attempt,
                   resolver));
  }
}

// Calls cryptohome's remove method.
void Remove(AuthAttemptState* attempt,
            scoped_refptr<ParallelAuthenticator> resolver) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  chromeos::BootTimesLoader::Get()->AddLoginTimeMarker(
      "CryptohomeRemove-Start", false);
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove(
      attempt->user_context.username,
      base::Bind(&TriggerResolveWithLoginTimeMarker,
                 "CryptohomeRemove-End",
                 attempt,
                 resolver));
}

// Calls cryptohome's key check method.
void CheckKey(AuthAttemptState* attempt,
              scoped_refptr<ParallelAuthenticator> resolver,
              const std::string& system_salt) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  cryptohome::AsyncMethodCaller::GetInstance()->AsyncCheckKey(
      attempt->user_context.username,
      ParallelAuthenticator::HashPassword(attempt->user_context.password,
                                          system_salt),
      base::Bind(&TriggerResolve, attempt, resolver));
}

}  // namespace

ParallelAuthenticator::ParallelAuthenticator(LoginStatusConsumer* consumer)
    : Authenticator(consumer),
      migrate_attempted_(false),
      remove_attempted_(false),
      resync_attempted_(false),
      ephemeral_mount_attempted_(false),
      check_key_attempted_(false),
      already_reported_success_(false),
      owner_is_verified_(false),
      user_can_login_(false),
      remove_user_data_on_failure_(false),
      delayed_login_failure_(NULL) {
}

void ParallelAuthenticator::AuthenticateToLogin(
    Profile* profile,
    const UserContext& user_context) {
  std::string canonicalized = gaia::CanonicalizeEmail(user_context.username);
  authentication_profile_ = profile;
  current_state_.reset(
      new AuthAttemptState(
          UserContext(canonicalized,
                      user_context.password,
                      user_context.auth_code),
          std::string(), // login_token, not used.
          std::string(), // login_captcha, not used.
          User::USER_TYPE_REGULAR,
          !UserManager::Get()->IsKnownUser(canonicalized)));
  // Reset the verified flag.
  owner_is_verified_ = false;

  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&Mount,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this),
                 cryptohome::MOUNT_FLAGS_NONE));
}

void ParallelAuthenticator::CompleteLogin(Profile* profile,
                                          const UserContext& user_context) {
  std::string canonicalized = gaia::CanonicalizeEmail(user_context.username);
  authentication_profile_ = profile;
  current_state_.reset(
      new AuthAttemptState(
          UserContext(canonicalized,
                      user_context.password,
                      user_context.auth_code,
                      user_context.username_hash,
                      user_context.using_oauth,
                      user_context.auth_flow),
          !UserManager::Get()->IsKnownUser(canonicalized)));

  // Reset the verified flag.
  owner_is_verified_ = false;

  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&Mount,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this),
                 cryptohome::MOUNT_FLAGS_NONE));

  // For login completion from extension, we just need to resolve the current
  // auth attempt state, the rest of OAuth related tasks will be done in
  // parallel.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&ParallelAuthenticator::ResolveLoginCompletionStatus, this));
}

void ParallelAuthenticator::AuthenticateToUnlock(
    const UserContext& user_context) {
  current_state_.reset(
      new AuthAttemptState(
          gaia::CanonicalizeEmail(user_context.username),
          user_context.password));
  remove_user_data_on_failure_ = false;
  check_key_attempted_ = true;
  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&CheckKey,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this)));
}

void ParallelAuthenticator::LoginAsLocallyManagedUser(
    const UserContext& user_context) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // TODO(nkostylev): Pass proper value for |user_is_new| or remove (not used).
  current_state_.reset(
      new AuthAttemptState(user_context,
                           "",   // login_token
                           "",   // login_captcha
                           User::USER_TYPE_LOCALLY_MANAGED,
                           false));
  remove_user_data_on_failure_ = false;
  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&Mount,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this),
                 cryptohome::MOUNT_FLAGS_NONE));
}

void ParallelAuthenticator::LoginRetailMode() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // Note: |kRetailModeUserEMail| is used in other places to identify a retail
  // mode session.
  current_state_.reset(new AuthAttemptState(
        UserContext(UserManager::kRetailModeUserName,
                    std::string(),   // password
                    std::string()),  // auth_code
        std::string(),  // login_token
        std::string(),  // login_captcha
        User::USER_TYPE_RETAIL_MODE,
        false));
  remove_user_data_on_failure_ = false;
  ephemeral_mount_attempted_ = true;
  MountGuest(current_state_.get(),
             scoped_refptr<ParallelAuthenticator>(this));
}

void ParallelAuthenticator::LoginOffTheRecord() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  current_state_.reset(new AuthAttemptState(
      UserContext(UserManager::kGuestUserName,  // username
                  std::string(),                // password
                  std::string()),               // auth_code
      std::string(),  // login_token
      std::string(),  // login_captcha
      User::USER_TYPE_GUEST,
      false));
  remove_user_data_on_failure_ = false;
  ephemeral_mount_attempted_ = true;
  MountGuest(current_state_.get(),
             scoped_refptr<ParallelAuthenticator>(this));
}

void ParallelAuthenticator::LoginAsPublicAccount(const std::string& username) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  current_state_.reset(new AuthAttemptState(
      UserContext(username,
                  std::string(),  // password
                  std::string()),  // auth_code
      std::string(),  // login_token
      std::string(),  // login_captcha
      User::USER_TYPE_PUBLIC_ACCOUNT,
      false));
  remove_user_data_on_failure_ = false;
  ephemeral_mount_attempted_ = true;
  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&Mount,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this),
                 cryptohome::CREATE_IF_MISSING | cryptohome::ENSURE_EPHEMERAL));
}

void ParallelAuthenticator::LoginAsKioskAccount(
    const std::string& app_user_id,
    bool use_guest_mount) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  const std::string user_id =
      use_guest_mount ? UserManager::kGuestUserName : app_user_id;
  current_state_.reset(new AuthAttemptState(
      UserContext(user_id,
                  std::string(),  // password
                  std::string()),  // auth_code
      std::string(),  // login_token
      std::string(),  // login_captcha
      User::USER_TYPE_KIOSK_APP,
      false));

  remove_user_data_on_failure_ = true;
  if (!use_guest_mount) {
    MountPublic(current_state_.get(),
          scoped_refptr<ParallelAuthenticator>(this),
          cryptohome::CREATE_IF_MISSING);
  } else {
    ephemeral_mount_attempted_ = true;
    MountGuestAndGetHash(current_state_.get(),
                         scoped_refptr<ParallelAuthenticator>(this));
  }
}

void ParallelAuthenticator::OnRetailModeLoginSuccess() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  VLOG(1) << "Retail mode login success";
  // Send notification of success
  AuthenticationNotificationDetails details(true);
  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
      content::NotificationService::AllSources(),
      content::Details<AuthenticationNotificationDetails>(&details));
  if (consumer_)
    consumer_->OnRetailModeLoginSuccess(current_state_->user_context);
}

void ParallelAuthenticator::OnLoginSuccess() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  VLOG(1) << "Login success";
  // Send notification of success
  AuthenticationNotificationDetails details(true);
  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
      content::NotificationService::AllSources(),
      content::Details<AuthenticationNotificationDetails>(&details));
  {
    base::AutoLock for_this_block(success_lock_);
    already_reported_success_ = true;
  }
  if (consumer_)
    consumer_->OnLoginSuccess(current_state_->user_context);
}

void ParallelAuthenticator::OnOffTheRecordLoginSuccess() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // Send notification of success
  AuthenticationNotificationDetails details(true);
  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
      content::NotificationService::AllSources(),
      content::Details<AuthenticationNotificationDetails>(&details));
  if (consumer_)
    consumer_->OnOffTheRecordLoginSuccess();
}

void ParallelAuthenticator::OnPasswordChangeDetected() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (consumer_)
    consumer_->OnPasswordChangeDetected();
}

void ParallelAuthenticator::OnLoginFailure(const LoginFailure& error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  // OnLoginFailure will be called again with the same |error|
  // after the cryptohome has been removed.
  if (remove_user_data_on_failure_) {
    delayed_login_failure_ = &error;
    RemoveEncryptedData();
    return;
  }

  // Send notification of failure
  AuthenticationNotificationDetails details(false);
  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
      content::NotificationService::AllSources(),
      content::Details<AuthenticationNotificationDetails>(&details));
  LOG(WARNING) << "Login failed: " << error.GetErrorString();
  if (consumer_)
    consumer_->OnLoginFailure(error);
}

void ParallelAuthenticator::RecoverEncryptedData(
    const std::string& old_password) {
  migrate_attempted_ = true;
  current_state_->ResetCryptohomeStatus();
  SystemSaltGetter::Get()->GetSystemSalt(
      base::Bind(&Migrate,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this),
                 true,
                 old_password));
}

void ParallelAuthenticator::RemoveEncryptedData() {
  remove_attempted_ = true;
  current_state_->ResetCryptohomeStatus();
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Remove,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this)));
}

void ParallelAuthenticator::ResyncEncryptedData() {
  resync_attempted_ = true;
  current_state_->ResetCryptohomeStatus();
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Remove,
                 current_state_.get(),
                 scoped_refptr<ParallelAuthenticator>(this)));
}

bool ParallelAuthenticator::VerifyOwner() {
  if (owner_is_verified_)
    return true;
  // Check if policy data is fine and continue in safe mode if needed.
  bool is_safe_mode = false;
  CrosSettings::Get()->GetBoolean(kPolicyMissingMitigationMode, &is_safe_mode);
  if (!is_safe_mode) {
    // Now we can continue with the login and report mount success.
    user_can_login_ = true;
    owner_is_verified_ = true;
    return true;
  }
  // Now we can continue reading the private key.
  DeviceSettingsService::Get()->SetUsername(
      current_state_->user_context.username);
  // This should trigger certificate loading, which is needed in order to
  // correctly determine if the current user is the owner.
  if (LoginState::IsInitialized()) {
    LoginState::Get()->SetLoggedInState(LoginState::LOGGED_IN_SAFE_MODE,
                                        LoginState::LOGGED_IN_USER_NONE);
  }
  DeviceSettingsService::Get()->IsCurrentUserOwnerAsync(
      base::Bind(&ParallelAuthenticator::OnOwnershipChecked, this));
  return false;
}

void ParallelAuthenticator::OnOwnershipChecked(bool is_owner) {
  // Now we can check if this user is the owner.
  user_can_login_ = is_owner;
  owner_is_verified_ = true;
  Resolve();
}

void ParallelAuthenticator::Resolve() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  int mount_flags = cryptohome::MOUNT_FLAGS_NONE;
  ParallelAuthenticator::AuthState state = ResolveState();
  VLOG(1) << "Resolved state to: " << state;
  switch (state) {
    case CONTINUE:
    case POSSIBLE_PW_CHANGE:
    case NO_MOUNT:
      // These are intermediate states; we need more info from a request that
      // is still pending.
      break;
    case FAILED_MOUNT:
      // In this case, whether login succeeded or not, we can't log
      // the user in because their data is horked.  So, override with
      // the appropriate failure.
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     LoginFailure(LoginFailure::COULD_NOT_MOUNT_CRYPTOHOME)));
      break;
    case FAILED_REMOVE:
      // In this case, we tried to remove the user's old cryptohome at her
      // request, and the remove failed.
      remove_user_data_on_failure_ = false;
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     LoginFailure(LoginFailure::DATA_REMOVAL_FAILED)));
      break;
    case FAILED_TMPFS:
      // In this case, we tried to mount a tmpfs for guest and failed.
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS)));
      break;
    case FAILED_TPM:
      // In this case, we tried to create/mount cryptohome and failed
      // because of the critical TPM error.
      // Chrome will notify user and request reboot.
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     LoginFailure(LoginFailure::TPM_ERROR)));
      break;
    case FAILED_USERNAME_HASH:
      // In this case, we failed the GetSanitizedUsername request to
      // cryptohomed. This can happen for any login attempt.
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     LoginFailure(LoginFailure::USERNAME_HASH_FAILED)));
      break;
    case REMOVED_DATA_AFTER_FAILURE:
      remove_user_data_on_failure_ = false;
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginFailure, this,
                     *delayed_login_failure_));
      break;
    case CREATE_NEW:
      mount_flags |= cryptohome::CREATE_IF_MISSING;
    case RECOVER_MOUNT:
      current_state_->ResetCryptohomeStatus();
      SystemSaltGetter::Get()->GetSystemSalt(
          base::Bind(&Mount,
                     current_state_.get(),
                     scoped_refptr<ParallelAuthenticator>(this),
                     mount_flags));
      break;
    case NEED_OLD_PW:
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnPasswordChangeDetected, this));
      break;
    case ONLINE_FAILED:
    case NEED_NEW_PW:
    case HAVE_NEW_PW:
      NOTREACHED() << "Using obsolete ClientLogin code path.";
      break;
    case OFFLINE_LOGIN:
      VLOG(2) << "Offline login";
      // Fall through.
    case UNLOCK:
      VLOG(2) << "Unlock";
      // Fall through.
    case ONLINE_LOGIN:
      VLOG(2) << "Online login";
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginSuccess, this));
      break;
    case DEMO_LOGIN:
      VLOG(2) << "Retail mode login";
      current_state_->user_context.using_oauth = false;
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnRetailModeLoginSuccess, this));
      break;
    case GUEST_LOGIN:
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnOffTheRecordLoginSuccess, this));
      break;
    case KIOSK_ACCOUNT_LOGIN:
    case PUBLIC_ACCOUNT_LOGIN:
      current_state_->user_context.using_oauth = false;
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginSuccess, this));
      break;
    case LOCALLY_MANAGED_USER_LOGIN:
      current_state_->user_context.using_oauth = false;
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(&ParallelAuthenticator::OnLoginSuccess, this));
      break;
    case LOGIN_FAILED:
      current_state_->ResetCryptohomeStatus();
      BrowserThread::PostTask(BrowserThread::UI,
                              FROM_HERE,
                              base::Bind(
                                  &ParallelAuthenticator::OnLoginFailure,
                                  this,
                                  current_state_->online_outcome()));
      break;
    case OWNER_REQUIRED: {
      current_state_->ResetCryptohomeStatus();
      bool success = false;
      DBusThreadManager::Get()->GetCryptohomeClient()->Unmount(&success);
      if (!success) {
        // Maybe we should reboot immediately here?
        LOG(ERROR) << "Couldn't unmount users home!";
      }
      BrowserThread::PostTask(BrowserThread::UI,
                              FROM_HERE,
                              base::Bind(
                                  &ParallelAuthenticator::OnLoginFailure,
                                  this,
                                  LoginFailure(LoginFailure::OWNER_REQUIRED)));
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

// static.
std::string ParallelAuthenticator::HashPassword(const std::string& password,
                                                const std::string& ascii_salt) {
  // Update sha with ascii encoded salt, then update with ascii of password,
  // then end.
  // TODO(stevenjb/nkostylev): Handle empty system salt gracefully.
  CHECK(!ascii_salt.empty());
  char passhash_buf[kPasswordHashLength];

  // Hash salt and password
  crypto::SHA256HashString(ascii_salt + password,
                           &passhash_buf, sizeof(passhash_buf));

  // Only want the top half for 'weak' hashing so that the passphrase is not
  // immediately exposed even if the output is reversed.
  const int encoded_length = sizeof(passhash_buf) / 2;

  return StringToLowerASCII(base::HexEncode(
      reinterpret_cast<const void*>(passhash_buf), encoded_length));
}

ParallelAuthenticator::~ParallelAuthenticator() {}

ParallelAuthenticator::AuthState ParallelAuthenticator::ResolveState() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // If we haven't mounted the user's home dir yet or
  // haven't got sanitized username value, we can't be done.
  // We never get past here if any of these two cryptohome ops is still pending.
  // This is an important invariant.
  if (!current_state_->cryptohome_complete() ||
      !current_state_->username_hash_obtained()) {
    return CONTINUE;
  }

  AuthState state = CONTINUE;

  if (current_state_->cryptohome_outcome() &&
      current_state_->username_hash_valid()) {
    state = ResolveCryptohomeSuccessState();
  } else {
    state = ResolveCryptohomeFailureState();
  }

  DCHECK(current_state_->cryptohome_complete());  // Ensure invariant holds.
  migrate_attempted_ = false;
  remove_attempted_ = false;
  resync_attempted_ = false;
  ephemeral_mount_attempted_ = false;
  check_key_attempted_ = false;

  if (state != POSSIBLE_PW_CHANGE &&
      state != NO_MOUNT &&
      state != OFFLINE_LOGIN)
    return state;

  if (current_state_->online_complete()) {
    if (current_state_->online_outcome().reason() == LoginFailure::NONE) {
      // Online attempt succeeded as well, so combine the results.
      return ResolveOnlineSuccessState(state);
    }
    NOTREACHED() << "Using obsolete ClientLogin code path.";
  }
  // if online isn't complete yet, just return the offline result.
  return state;
}

ParallelAuthenticator::AuthState
ParallelAuthenticator::ResolveCryptohomeFailureState() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (remove_attempted_ || resync_attempted_)
    return FAILED_REMOVE;
  if (ephemeral_mount_attempted_)
    return FAILED_TMPFS;
  if (migrate_attempted_)
    return NEED_OLD_PW;
  if (check_key_attempted_)
    return LOGIN_FAILED;

  if (current_state_->cryptohome_code() ==
      cryptohome::MOUNT_ERROR_TPM_NEEDS_REBOOT) {
    // Critical TPM error detected, reboot needed.
    return FAILED_TPM;
  }

  // Return intermediate states in the following case:
  // when there is an online result to use;
  // This is the case after user finishes Gaia login;
  if (current_state_->online_complete()) {
    if (current_state_->cryptohome_code() ==
        cryptohome::MOUNT_ERROR_KEY_FAILURE) {
      // If we tried a mount but they used the wrong key, we may need to
      // ask the user for her old password.  We'll only know once we've
      // done the online check.
      return POSSIBLE_PW_CHANGE;
    }
    if (current_state_->cryptohome_code() ==
        cryptohome::MOUNT_ERROR_USER_DOES_NOT_EXIST) {
      // If we tried a mount but the user did not exist, then we should wait
      // for online login to succeed and try again with the "create" flag set.
      return NO_MOUNT;
    }
  }

  if (!current_state_->username_hash_valid())
    return FAILED_USERNAME_HASH;

  return FAILED_MOUNT;
}

ParallelAuthenticator::AuthState
ParallelAuthenticator::ResolveCryptohomeSuccessState() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (resync_attempted_)
    return CREATE_NEW;
  if (remove_attempted_)
    return REMOVED_DATA_AFTER_FAILURE;
  if (migrate_attempted_)
    return RECOVER_MOUNT;
  if (check_key_attempted_)
    return UNLOCK;

  if (current_state_->user_type == User::USER_TYPE_GUEST)
    return GUEST_LOGIN;
  if (current_state_->user_type == User::USER_TYPE_RETAIL_MODE)
    return DEMO_LOGIN;
  if (current_state_->user_type == User::USER_TYPE_PUBLIC_ACCOUNT)
    return PUBLIC_ACCOUNT_LOGIN;
  if (current_state_->user_type == User::USER_TYPE_KIOSK_APP)
    return KIOSK_ACCOUNT_LOGIN;
  if (current_state_->user_type == User::USER_TYPE_LOCALLY_MANAGED)
    return LOCALLY_MANAGED_USER_LOGIN;

  if (!VerifyOwner())
    return CONTINUE;
  return user_can_login_ ? OFFLINE_LOGIN : OWNER_REQUIRED;
}

ParallelAuthenticator::AuthState
ParallelAuthenticator::ResolveOnlineSuccessState(
    ParallelAuthenticator::AuthState offline_state) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  switch (offline_state) {
    case POSSIBLE_PW_CHANGE:
      return NEED_OLD_PW;
    case NO_MOUNT:
      return CREATE_NEW;
    case OFFLINE_LOGIN:
      return ONLINE_LOGIN;
    default:
      NOTREACHED();
      return offline_state;
  }
}

void ParallelAuthenticator::ResolveLoginCompletionStatus() {
  // Shortcut online state resolution process.
  current_state_->RecordOnlineLoginStatus(LoginFailure::LoginFailureNone());
  Resolve();
}

void ParallelAuthenticator::SetOwnerState(bool owner_check_finished,
                                          bool check_result) {
  owner_is_verified_ = owner_check_finished;
  user_can_login_ = check_result;
}

}  // namespace chromeos

/* [<][>][^][v][top][bottom][index][help] */