chromium/chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"

#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chromeos/ash/components/cryptohome/constants.h"
#include "chromeos/ash/components/cryptohome/error_types.h"
#include "chromeos/ash/components/cryptohome/error_util.h"
#include "chromeos/ash/components/dbus/cryptohome/UserDataAuth.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/auth_factor.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/recoverable_key_store.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "third_party/abseil-cpp/absl/types/variant.h"

namespace ash {

using ::user_data_auth::CryptohomeErrorCode;

namespace {

// Specialized structs for each auth factor with factor-specific metadata.
// Secrets are stored the same way they are sent to cryptohome (i.e. salted and
// hashed), but only if secret checking has been enabled via
// `TestApi::set_enabled_auth_check`.
// `FakeAuthFactor` is the union/absl::variant of the factor-specific auth
// factor structs.

struct PasswordFactor {
  // This will be `std::nullopt` if auth checking hasn't been activated.
  std::optional<std::string> password;
};

struct PinFactor {
  // This will be `std::nullopt` if auth checking hasn't been activated.
  std::optional<std::string> pin = std::nullopt;
  bool locked = false;
};

struct RecoveryFactor {};
struct SmartCardFactor {
  std::string public_key_spki_der;
};

struct KioskFactor {};

using FakeAuthFactor = absl::variant<PasswordFactor,
                                     PinFactor,
                                     RecoveryFactor,
                                     KioskFactor,
                                     SmartCardFactor>;

// Strings concatenated with the account id to obtain a user's profile
// directory name. The prefix "u-" below corresponds to
// `chrome::kProfileDirPrefix` (which can not be easily included here) and
// "-hash" is as in `GetStubSanitizedUsername`.
const std::string kUserDataDirNamePrefix = "u-";
const std::string kUserDataDirNameSuffix = "-hash";

// Label of the recovery auth factor.
// Label of the kiosk auth factor.
const std::string kCryptohomePublicMountLabel = "publicmount";

// Labels used of of various types of auth factors used by chrome. These must
// be kept in sync with the labels in cryptohome_key_constants.{cc,h}, which
// cannot be included into this file because that would result in circular
// dependencies.
const std::string kCryptohomeGaiaKeyLabel = "gaia";
const std::string kCryptohomeRecoveryKeyLabel = "recovery";
const std::string kCryptohomeLocalPasswordKeyLabel = "local-password";

template <typename ReplyType>
void SetErrorWrapperToReply(ReplyType& reply, cryptohome::ErrorWrapper error) {
  reply.set_error(error.code());
}

}  // namespace

struct FakeUserDataAuthClient::UserCryptohomeState {
  // Maps labels to auth factors.
  base::flat_map<std::string, FakeAuthFactor> auth_factors;

  // A flag describing how we pretend that the user's home directory is
  // encrypted.
  HomeEncryptionMethod home_encryption_method =
      HomeEncryptionMethod::kDirCrypto;

  // A flag describing how we pretend that the user's home directory migration
  // was not completed correctly.
  bool incomplete_migration = false;

  // If users are created in test constructor, UserDataDir is not available
  // yet, so actual directory creation needs to be postponed.
  bool postponed_directory_creation = false;

  // Is the user ephemeral.
  bool ephemeral = false;
};

namespace {

// Interval to update the progress of MigrateToDircrypto in milliseconds.
constexpr int kDircryptoMigrationUpdateIntervalMs = 200;
// The number of updates the MigrateToDircrypto will send before it completes.
constexpr uint64_t kDircryptoMigrationMaxProgress = 15;

// Template for auth session ID.
constexpr char kAuthSessionIdTemplate[] = "AuthSession-%d";

// Guest username constant that mirrors the one in real cryptohome
constexpr char kGuestUserName[] = "$guest";

// Used to track the global fake instance. This global fake instance is created
// in the first call to FakeUserDataAuth::Get(). During browser startup in
// browser tests and cros-linux, the global instance pointer in
// userdataauth_client.cc is set to the address of this global fake instance.
// During shutdown, the fake instance is deleted in the same way as the normal
// UserDataAuth instance would be deleted. We do this to stay as faithful as
// possible to the real implementation.
// However, browser tests can access and configure the fake instance via the
// TestApi or CryptohomeMixin even before the browser starts, for example in
// the constructor of a browser test fixture.
FakeUserDataAuthClient* g_instance = nullptr;

// `OverloadedFunctor` and `FunctorWithReturnType` are used to implement
// `Overload`, which constructs a visitor appropriate for use with
// `absl::visit` from lambdas for each case.

// A functor combining the `operator()` definitions of a list of functors into
// a single functor with overloaded `operator()`.
template <class... Functors>
struct OverloadedFunctor : Functors... {
  using Functors::operator()...;
};

// Used to fix the return type of a functor with overloaded `operator()`.
// This is useful in case the `operator()` overloads have different return
// types, but all return types are convertible into the intended fixed
// `ReturnType`.
template <class ReturnType, class Functor>
struct FunctorWithReturnType {
  template <class Arg>
  ReturnType operator()(Arg&& arg) {
    return functor(std::forward<Arg>(arg));
  }

  Functor functor;
};

// `Overload` constructs a visitor appropriate for use with `absl::visit` from
// a number of lambdas for each case. The return type of each provided lambda
// must be convertible to `ReturnType`, and the `operator()` of the combined
// visitor will always return `ReturnType`.
template <class ReturnType, class... Functors>
FunctorWithReturnType<ReturnType, OverloadedFunctor<Functors...>> Overload(
    Functors... functors) {
  return {{std::move(functors)...}};
}

std::optional<user_data_auth::AuthFactor> FakeAuthFactorToAuthFactor(
    std::string label,
    const FakeAuthFactor& factor) {
  return absl::visit(
      Overload<std::optional<user_data_auth::AuthFactor>>(
          [&](const PasswordFactor& password) {
            user_data_auth::AuthFactor result;
            result.set_label(std::move(label));
            result.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
            result.mutable_password_metadata();
            return result;
          },
          [&](const PinFactor& pin) {
            user_data_auth::AuthFactor result;
            result.set_label(std::move(label));
            result.set_type(user_data_auth::AUTH_FACTOR_TYPE_PIN);
            result.mutable_pin_metadata()->set_auth_locked(pin.locked);
            return result;
          },
          [&](const RecoveryFactor&) {
            user_data_auth::AuthFactor result;
            result.set_label(std::move(label));
            result.set_type(
                user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY);
            result.mutable_cryptohome_recovery_metadata();
            return result;
          },
          [&](const KioskFactor& kiosk) {
            user_data_auth::AuthFactor result;
            result.set_label(std::move(label));
            result.set_type(user_data_auth::AUTH_FACTOR_TYPE_KIOSK);
            result.mutable_kiosk_metadata();
            return result;
          },
          [&](const SmartCardFactor& smart_card) {
            user_data_auth::AuthFactor result;
            result.set_label(std::move(label));
            result.set_type(user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD);
            result.mutable_smart_card_metadata()->set_public_key_spki_der(
                smart_card.public_key_spki_der);
            return result;
          }),
      factor);
}

std::optional<cryptohome::RecoverableKeyStore>
FakeAuthFactorToRecoverableKeyStore(const FakeAuthFactor& factor) {
  return absl::visit(
      Overload<std::optional<cryptohome::RecoverableKeyStore>>(
          [&](const PasswordFactor& password) {
            cryptohome::RecoverableKeyStore store;
            store.mutable_key_store_metadata()->set_knowledge_factor_type(
                cryptohome::KNOWLEDGE_FACTOR_TYPE_PASSWORD);
            store.mutable_wrapped_security_domain_key()->set_key_name(
                "security_domain_member_key_encrypted_locally");
            return store;
          },
          [&](const PinFactor& pin) {
            cryptohome::RecoverableKeyStore store;
            store.mutable_key_store_metadata()->set_knowledge_factor_type(
                cryptohome::KNOWLEDGE_FACTOR_TYPE_PIN);
            store.mutable_wrapped_security_domain_key()->set_key_name(
                "security_domain_member_key_encrypted_locally");
            return store;
          },
          [&](const auto&) { return std::nullopt; }),
      factor);
}

// Turns AuthFactor+AuthInput into a pair of label and FakeAuthFactor.
std::pair<std::string, FakeAuthFactor> AuthFactorWithInputToFakeAuthFactor(
    const user_data_auth::AuthFactor& factor,
    const user_data_auth::AuthInput& input,
    bool save_secret) {
  const std::string& label = factor.label();
  CHECK_NE(label, "") << "Key label must not be empty string";

  std::optional<std::string> secret = std::nullopt;
  if (save_secret) {
    if (factor.type() == user_data_auth::AUTH_FACTOR_TYPE_PASSWORD) {
      secret = input.password_input().secret();
    } else if (factor.type() == user_data_auth::AUTH_FACTOR_TYPE_PIN) {
      secret = input.pin_input().secret();
    }
  }

  switch (factor.type()) {
    case user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED:
      LOG(FATAL) << "Chrome should never send Unspecified auth factor.";
      __builtin_unreachable();
    case user_data_auth::AUTH_FACTOR_TYPE_PIN:
      return {label, PinFactor{.pin = secret, .locked = false}};
    case user_data_auth::AUTH_FACTOR_TYPE_PASSWORD:
      return {label, PasswordFactor{.password = secret}};
    case user_data_auth::AUTH_FACTOR_TYPE_KIOSK:
      return {label, KioskFactor{}};
    case user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY:
      return {label, RecoveryFactor{}};
    case user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD: {
      std::string key = factor.smart_card_metadata().public_key_spki_der();
      return {label, SmartCardFactor{.public_key_spki_der = key}};
    }
    default:
      NOTREACHED_IN_MIGRATION();
      __builtin_unreachable();
  }
}

bool CheckCredentialsViaAuthFactor(const FakeAuthFactor& factor,
                                   const std::string& secret) {
  return absl::visit(
      Overload<bool>(
          [&](const PasswordFactor& password) {
            return password.password == secret;
          },
          [&](const PinFactor& pin) { return pin.pin == secret; },
          [&](const RecoveryFactor& recovery) -> bool {
            LOG(FATAL) << "Checking recovery key is not allowed";
          },
          [&](const KioskFactor& kiosk) {
            // Kiosk key secrets are derived from app ids and don't leave
            // cryptohome, so there's nothing to check.
            return true;
          },
          [&](const SmartCardFactor& smart_card) -> bool {
            LOG(FATAL) << "Checking smart card key is not implemented yet";
          }),
      factor);
}

template <class FakeFactorType>
bool ContainsFakeFactor(
    const base::flat_map<std::string, FakeAuthFactor>& factors) {
  const auto it =
      base::ranges::find_if(factors, [](const auto label_factor_pair) {
        const FakeAuthFactor& fake_factor = label_factor_pair.second;
        return absl::get_if<FakeFactorType>(&fake_factor) != nullptr;
      });
  return it != std::end(factors);
}

bool AuthInputMatchesFakeFactorType(
    const ::user_data_auth::AuthInput& auth_input,
    const FakeAuthFactor& fake_factor) {
  return absl::visit(
      Overload<bool>(
          [&](const PasswordFactor& password) {
            return auth_input.has_password_input();
          },
          [&](const PinFactor& pin) { return auth_input.has_pin_input(); },
          [&](const RecoveryFactor& recovery) {
            return auth_input.has_cryptohome_recovery_input();
          },
          [&](const KioskFactor& kiosk) {
            return auth_input.has_kiosk_input();
          },
          [&](const SmartCardFactor& smart_card) {
            return auth_input.has_smart_card_input();
          }),
      fake_factor);
}

// Helper that fills AuthFactorWithStatus' field with an auth factor.
void BuildAuthFactorWithStatus(
    const std::optional<user_data_auth::AuthFactor>& auth_factor,
    user_data_auth::AuthFactorWithStatus* factor_with_status) {
  *factor_with_status->mutable_auth_factor() = *auth_factor;
  // Add all possible intents for conveniences.
  factor_with_status->add_available_for_intents(
      user_data_auth::AUTH_INTENT_DECRYPT);
  factor_with_status->add_available_for_intents(
      user_data_auth::AUTH_INTENT_VERIFY_ONLY);
  factor_with_status->add_available_for_intents(
      user_data_auth::AUTH_INTENT_WEBAUTHN);
  factor_with_status->mutable_status_info()->set_time_available_in(0);
  if (auth_factor->type() == user_data_auth::AUTH_FACTOR_TYPE_PIN) {
    if (auth_factor->pin_metadata().auth_locked()) {
      factor_with_status->mutable_status_info()->set_time_available_in(
          std::numeric_limits<uint64_t>::max());
      factor_with_status->mutable_status_info()->set_time_expiring_in(
          std::numeric_limits<uint64_t>::max());
    }
  }
}

// Helper that automatically sends a reply struct to a supplied callback when
// it goes out of scope. Basically a specialized `absl::Cleanup` or
// `std::scope_exit`.
template <typename ReplyType>
class ReplyOnReturn {
 public:
  explicit ReplyOnReturn(ReplyType* reply,
                         chromeos::DBusMethodCallback<ReplyType> callback)
      : reply_(reply), callback_(std::move(callback)) {}
  ReplyOnReturn(const ReplyOnReturn<ReplyType>&) = delete;

  ~ReplyOnReturn() {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback_), *reply_));
  }

  ReplyOnReturn<ReplyType>& operator=(const ReplyOnReturn<ReplyType>&) = delete;

 private:
  raw_ptr<ReplyType> reply_;
  chromeos::DBusMethodCallback<ReplyType> callback_;
};

user_data_auth::VaultEncryptionType HomeEncryptionMethodToVaultEncryptionType(
    const FakeUserDataAuthClient::HomeEncryptionMethod home_encryption) {
  switch (home_encryption) {
    case FakeUserDataAuthClient::HomeEncryptionMethod::kDirCrypto:
      return user_data_auth::CRYPTOHOME_VAULT_ENCRYPTION_FSCRYPT;
    case FakeUserDataAuthClient::HomeEncryptionMethod::kEcryptfs:
      return user_data_auth::CRYPTOHOME_VAULT_ENCRYPTION_ECRYPTFS;
    case FakeUserDataAuthClient::HomeEncryptionMethod::kDmCrypt:
      return user_data_auth::CRYPTOHOME_VAULT_ENCRYPTION_DMCRYPT;
  }
}
}  // namespace

// =============== `AuthSessionData` =====================
FakeUserDataAuthClient::AuthSessionData::AuthSessionData() = default;
FakeUserDataAuthClient::AuthSessionData::AuthSessionData(
    const AuthSessionData& other) = default;
FakeUserDataAuthClient::AuthSessionData&
FakeUserDataAuthClient::AuthSessionData::operator=(const AuthSessionData&) =
    default;
FakeUserDataAuthClient::AuthSessionData::~AuthSessionData() = default;

// static
FakeUserDataAuthClient::TestApi* FakeUserDataAuthClient::TestApi::Get() {
  static TestApi instance;
  return &instance;
}

// static
void FakeUserDataAuthClient::TestApi::OverrideGlobalInstance(
    std::unique_ptr<FakeUserDataAuthClient> client) {
  CHECK(!g_instance);
  g_instance = client.release();
}

void FakeUserDataAuthClient::TestApi::SetServiceIsAvailable(bool is_available) {
  FakeUserDataAuthClient::Get()->service_is_available_ = is_available;
  if (!is_available) {
    return;
  }
  FakeUserDataAuthClient::Get()
      ->RunPendingWaitForServiceToBeAvailableCallbacks();
}

void FakeUserDataAuthClient::TestApi::ReportServiceIsNotAvailable() {
  DCHECK(!FakeUserDataAuthClient::Get()->service_is_available_);
  FakeUserDataAuthClient::Get()->service_reported_not_available_ = true;
  FakeUserDataAuthClient::Get()
      ->RunPendingWaitForServiceToBeAvailableCallbacks();
}

void FakeUserDataAuthClient::TestApi::SetHomeEncryptionMethod(
    const cryptohome::AccountIdentifier& cryptohome_id,
    HomeEncryptionMethod method) {
  auto user_it = FakeUserDataAuthClient::Get()->users_.find(cryptohome_id);
  if (user_it == std::end(FakeUserDataAuthClient::Get()->users_)) {
    LOG(ERROR) << "User does not exist: " << cryptohome_id.account_id();
    // TODO(crbug.com/1334538): Some existing tests rely on us creating the
    // user here, but new tests shouldn't. Eventually this should crash.
    user_it = FakeUserDataAuthClient::Get()
                  ->users_.insert({cryptohome_id, UserCryptohomeState()})
                  .first;
  }
  DCHECK(user_it != std::end(FakeUserDataAuthClient::Get()->users_));
  UserCryptohomeState& user_state = user_it->second;
  user_state.home_encryption_method = method;
}

void FakeUserDataAuthClient::TestApi::SetEncryptionMigrationIncomplete(
    const cryptohome::AccountIdentifier& cryptohome_id,
    bool incomplete) {
  auto user_it = FakeUserDataAuthClient::Get()->users_.find(cryptohome_id);
  if (user_it == std::end(FakeUserDataAuthClient::Get()->users_)) {
    LOG(ERROR) << "User does not exist: " << cryptohome_id.account_id();
    // TODO(crbug.com/1334538): Some existing tests rely on us creating the
    // user here, but new tests shouldn't. Eventually this should crash.
    user_it = FakeUserDataAuthClient::Get()
                  ->users_.insert({cryptohome_id, UserCryptohomeState()})
                  .first;
  }
  DCHECK(user_it != std::end(FakeUserDataAuthClient::Get()->users_));
  UserCryptohomeState& user_state = user_it->second;
  user_state.incomplete_migration = incomplete;
}

void FakeUserDataAuthClient::TestApi::SetPinLocked(
    const cryptohome::AccountIdentifier& account_id,
    const std::string& label,
    bool locked) {
  auto user_it = FakeUserDataAuthClient::Get()->users_.find(account_id);
  CHECK(user_it != FakeUserDataAuthClient::Get()->users_.end())
      << "User does not exist: " << account_id.account_id();
  UserCryptohomeState& user_state = user_it->second;

  auto factor_it = user_state.auth_factors.find(label);
  CHECK(factor_it != user_state.auth_factors.end())
      << "Factor does not exist: " << label;
  FakeAuthFactor& factor = factor_it->second;

  PinFactor* pin_factor = absl::get_if<PinFactor>(&factor);
  CHECK(pin_factor) << "Factor is not PIN: " << label;

  pin_factor->locked = locked;
}

void FakeUserDataAuthClient::TestApi::AddExistingUser(
    const cryptohome::AccountIdentifier& account_id) {
  const auto [user_it, was_inserted] =
      FakeUserDataAuthClient::Get()->users_.insert(
          {std::move(account_id), UserCryptohomeState()});
  if (!was_inserted) {
    LOG(WARNING) << "User already exists: " << user_it->first.account_id();
    return;
  }

  const std::optional<base::FilePath> profile_dir =
      FakeUserDataAuthClient::Get()->GetUserProfileDir(user_it->first);
  if (!profile_dir) {
    LOG(WARNING) << "User data directory has not been set, will not create "
                    "user profile directory";
    user_it->second.postponed_directory_creation = true;
    return;
  }
  base::ScopedAllowBlockingForTesting allow_blocking;
  CHECK(base::CreateDirectory(*profile_dir));
}

std::optional<base::FilePath>
FakeUserDataAuthClient::TestApi::GetUserProfileDir(
    const cryptohome::AccountIdentifier& account_id) const {
  return FakeUserDataAuthClient::Get()->GetUserProfileDir(account_id);
}

void FakeUserDataAuthClient::TestApi::CreatePostponedDirectories() {
  base::ScopedAllowBlockingForTesting allow_blocking;
  for (auto& user_it : FakeUserDataAuthClient::Get()->users_) {
    if (!user_it.second.postponed_directory_creation) {
      continue;
    }
    const std::optional<base::FilePath> profile_dir =
        FakeUserDataAuthClient::Get()->GetUserProfileDir(user_it.first);
    CHECK(profile_dir) << "User data directory has not been set";
    CHECK(base::CreateDirectory(*profile_dir));
  }
}

void FakeUserDataAuthClient::TestApi::AddAuthFactor(
    const cryptohome::AccountIdentifier& account_id,
    const user_data_auth::AuthFactor& factor,
    const user_data_auth::AuthInput& input) {
  UserCryptohomeState& user_state = GetUserState(account_id);

  const auto [factor_it, was_inserted] = user_state.auth_factors.insert(
      AuthFactorWithInputToFakeAuthFactor(factor, input, true));
  CHECK(was_inserted) << "Factor already exists";
}

void FakeUserDataAuthClient::TestApi::AddRecoveryFactor(
    const cryptohome::AccountIdentifier& account_id) {
  UserCryptohomeState& user_state = GetUserState(account_id);

  FakeAuthFactor factor{RecoveryFactor()};
  const auto [factor_it, was_inserted] = user_state.auth_factors.insert(
      {kCryptohomeRecoveryKeyLabel, std::move(factor)});
  CHECK(was_inserted) << "Factor already exists";
}

bool FakeUserDataAuthClient::TestApi::HasRecoveryFactor(
    const cryptohome::AccountIdentifier& account_id) {
  const UserCryptohomeState& user_state = GetUserState(account_id);
  return ContainsFakeFactor<RecoveryFactor>(user_state.auth_factors);
}

bool FakeUserDataAuthClient::TestApi::HasPinFactor(
    const cryptohome::AccountIdentifier& account_id) {
  const UserCryptohomeState& user_state = GetUserState(account_id);
  return ContainsFakeFactor<PinFactor>(user_state.auth_factors);
}

std::pair<std::string, std::string> FakeUserDataAuthClient::TestApi::AddSession(
    const cryptohome::AccountIdentifier& account_id,
    bool authenticated) {
  CHECK(FakeUserDataAuthClient::Get()->users_.contains(account_id));

  std::string auth_session_id = base::StringPrintf(
      kAuthSessionIdTemplate,
      FakeUserDataAuthClient::Get()->next_auth_session_id_++);

  CHECK_EQ(FakeUserDataAuthClient::Get()->auth_sessions_.count(auth_session_id),
           0u);
  AuthSessionData& session =
      FakeUserDataAuthClient::Get()->auth_sessions_[auth_session_id];

  session.id = auth_session_id;
  session.broadcast_id = "b-" + auth_session_id;
  session.ephemeral = false;
  session.account = account_id;
  session.authenticated = authenticated;

  return {auth_session_id, session.broadcast_id};
}

bool FakeUserDataAuthClient::TestApi::IsAuthenticated(
    const cryptohome::AccountIdentifier& account_id) {
  CHECK(FakeUserDataAuthClient::Get()->users_.contains(account_id));

  auto& auth_sessions = FakeUserDataAuthClient::Get()->auth_sessions_;

  auto [auth_session_id, session] =
      *find_if(std::begin(auth_sessions), std::end(auth_sessions),
               [&account_id](auto session_entry) {
                 return session_entry.second.account == account_id;
               });

  return session.authenticated;
}

bool FakeUserDataAuthClient::TestApi::IsCurrentSessionEphemeral() {
  CHECK_EQ(FakeUserDataAuthClient::Get()->auth_sessions_.size(), 1u);
  return FakeUserDataAuthClient::Get()
      ->auth_sessions_.begin()
      ->second.ephemeral;
}

void FakeUserDataAuthClient::TestApi::DestroySessions() {
  g_instance->auth_sessions_.clear();
}

FakeUserDataAuthClient::UserCryptohomeState&
FakeUserDataAuthClient::TestApi::GetUserState(
    const cryptohome::AccountIdentifier& account_id) {
  const auto user_it = FakeUserDataAuthClient::Get()->users_.find(account_id);
  CHECK(user_it != std::end(FakeUserDataAuthClient::Get()->users_))
      << "User doesn't exist";
  return user_it->second;
}

void FakeUserDataAuthClient::TestApi::SendLegacyFPAuthSignal(
    user_data_auth::FingerprintScanResult result) {
  for (auto& observer : g_instance->fingerprint_observers_) {
    observer.OnFingerprintScan(result);
  }
}

void FakeUserDataAuthClient::TestApi::SetNextOperationError(
    Operation operation,
    cryptohome::ErrorWrapper error) {
  FakeUserDataAuthClient::Get()->SetNextOperationError(operation, error);
}

FakeUserDataAuthClient::FakeUserDataAuthClient() = default;

FakeUserDataAuthClient::~FakeUserDataAuthClient() {
  if (this == g_instance) {
    // If we're deleting the global instance, clear the pointer to it.
    g_instance = nullptr;
  }
}

// static
FakeUserDataAuthClient* FakeUserDataAuthClient::Get() {
  if (!g_instance) {
    g_instance = new FakeUserDataAuthClient();
  }
  return g_instance;
}

void FakeUserDataAuthClient::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void FakeUserDataAuthClient::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

void FakeUserDataAuthClient::AddFingerprintAuthObserver(
    FingerprintAuthObserver* observer) {
  fingerprint_observers_.AddObserver(observer);
}

void FakeUserDataAuthClient::RemoveFingerprintAuthObserver(
    FingerprintAuthObserver* observer) {
  fingerprint_observers_.RemoveObserver(observer);
}

void FakeUserDataAuthClient::AddPrepareAuthFactorProgressObserver(
    PrepareAuthFactorProgressObserver* observer) {
  progress_observers_.AddObserver(observer);
}

void FakeUserDataAuthClient::RemovePrepareAuthFactorProgressObserver(
    PrepareAuthFactorProgressObserver* observer) {
  progress_observers_.RemoveObserver(observer);
}

void FakeUserDataAuthClient::AddAuthFactorStatusUpdateObserver(
    AuthFactorStatusUpdateObserver* observer) {
  auth_factor_status_observer_list_.AddObserver(observer);
}

void FakeUserDataAuthClient::RemoveAuthFactorStatusUpdateObserver(
    AuthFactorStatusUpdateObserver* observer) {
  auth_factor_status_observer_list_.RemoveObserver(observer);
}

void FakeUserDataAuthClient::IsMounted(
    const ::user_data_auth::IsMountedRequest& request,
    IsMountedCallback callback) {
  ::user_data_auth::IsMountedReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  bool result;
  if (request.username().empty()) {
    result = !mounted_user_dirs_.empty();
  } else {
    result =
        mounted_user_dirs_.find(request.username()) != mounted_user_dirs_.end();
  }
  reply.set_is_mounted(result);
}

void FakeUserDataAuthClient::GetVaultProperties(
    const ::user_data_auth::GetVaultPropertiesRequest& request,
    GetVaultPropertiesCallback callback) {
  ::user_data_auth::GetVaultPropertiesReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  if (request.username().empty()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }
  cryptohome::AccountIdentifier account;
  account.set_account_id(request.username());
  const auto user_it = users_.find(account);

  // User does not exist, return an error.
  if (user_it != std::end(users_)) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }

  // User is not mounted, return an error.
  if (mounted_user_dirs_.find(request.username()) == mounted_user_dirs_.end()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }

  const auto user_state = user_it->second;

  // If ephemeral, then return error.
  if (user_state.ephemeral) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }

  reply.set_encryption_type(HomeEncryptionMethodToVaultEncryptionType(
      user_state.home_encryption_method));
}

void FakeUserDataAuthClient::Unmount(
    const ::user_data_auth::UnmountRequest& request,
    UnmountCallback callback) {
  ::user_data_auth::UnmountReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  mounted_user_dirs_.clear();
}

void FakeUserDataAuthClient::Remove(
    const ::user_data_auth::RemoveRequest& request,
    RemoveCallback callback) {
  RememberRequest<Operation::kRemove>(request);
  ::user_data_auth::RemoveReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  cryptohome::AccountIdentifier account_id;
  if (request.has_identifier()) {
    account_id = request.identifier();
  } else {
    auto auth_session = auth_sessions_.find(request.auth_session_id());
    CHECK(auth_session != std::end(auth_sessions_)) << "Invalid auth session";
    account_id = auth_session->second.account;
  }

  const auto user_it = users_.find(account_id);
  if (user_it == users_.end()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   ::user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND));
    return;
  }

  const std::optional<base::FilePath> profile_dir =
      GetUserProfileDir(account_id);
  if (profile_dir) {
    base::ScopedAllowBlockingForTesting allow_blocking;
    CHECK(base::DeletePathRecursively(*profile_dir));
  } else {
    LOG(WARNING) << "User data directory has not been set, will not delete "
                    "user profile directory";
  }

  users_.erase(user_it);
  if (!request.auth_session_id().empty()) {
    // Removing the user also invalidates the AuthSession.
    auth_sessions_.erase(request.auth_session_id());
  }
}

void FakeUserDataAuthClient::StartMigrateToDircrypto(
    const ::user_data_auth::StartMigrateToDircryptoRequest& request,
    StartMigrateToDircryptoCallback callback) {
  ::user_data_auth::StartMigrateToDircryptoReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kStartMigrateToDircrypto>(request);

  dircrypto_migration_progress_ = 0;

  if (run_default_dircrypto_migration_) {
    dircrypto_migration_progress_timer_.Start(
        FROM_HERE, base::Milliseconds(kDircryptoMigrationUpdateIntervalMs),
        this, &FakeUserDataAuthClient::OnDircryptoMigrationProgressUpdated);
  }
}
void FakeUserDataAuthClient::NeedsDircryptoMigration(
    const ::user_data_auth::NeedsDircryptoMigrationRequest& request,
    NeedsDircryptoMigrationCallback callback) {
  ::user_data_auth::NeedsDircryptoMigrationReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  const cryptohome::AccountIdentifier& account_id = request.account_id();

  const auto user_it = users_.find(account_id);
  if (user_it == std::end(users_)) {
    // TODO(crbug.com/1334538): New tests shouldn't rely on this behavior and
    // instead set up the user first.
    LOG(ERROR) << "User does not exist: " << account_id.account_id();
    reply.set_needs_dircrypto_migration(false);
    return;
  }
  DCHECK(user_it != users_.end());
  const UserCryptohomeState& user_state = user_it->second;

  const bool is_ecryptfs =
      user_state.home_encryption_method == HomeEncryptionMethod::kEcryptfs;
  reply.set_needs_dircrypto_migration(is_ecryptfs);
}
void FakeUserDataAuthClient::GetSupportedKeyPolicies(
    const ::user_data_auth::GetSupportedKeyPoliciesRequest& request,
    GetSupportedKeyPoliciesCallback callback) {
  ::user_data_auth::GetSupportedKeyPoliciesReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  reply.set_low_entropy_credentials_supported(
      supports_low_entropy_credentials_);
}
void FakeUserDataAuthClient::GetAccountDiskUsage(
    const ::user_data_auth::GetAccountDiskUsageRequest& request,
    GetAccountDiskUsageCallback callback) {
  ::user_data_auth::GetAccountDiskUsageReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  // Sets 100 MB as a fake usage.
  reply.set_size(100 * 1024 * 1024);
}

void FakeUserDataAuthClient::StartAuthSession(
    const ::user_data_auth::StartAuthSessionRequest& request,
    StartAuthSessionCallback callback) {
  ::user_data_auth::StartAuthSessionReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kStartAuthSession>(request);

  if (auto error = TakeOperationError(Operation::kStartAuthSession);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  std::string auth_session_id =
      base::StringPrintf(kAuthSessionIdTemplate, next_auth_session_id_++);

  DCHECK_EQ(auth_sessions_.count(auth_session_id), 0u);
  AuthSessionData& session = auth_sessions_[auth_session_id];
  session.id = auth_session_id;
  session.broadcast_id = "b-" + auth_session_id;
  session.ephemeral = request.is_ephemeral_user();
  session.account = request.account_id();
  session.requested_auth_session_intent = request.intent();

  reply.set_auth_session_id(auth_session_id);
  reply.set_broadcast_id(session.broadcast_id);

  const auto user_it = users_.find(request.account_id());
  const bool user_exists = user_it != std::end(users_);
  reply.set_user_exists(user_exists);

  const std::string& account_id = request.account_id().account_id();
  // See device_local_account.h
  const bool is_kiosk =
      base::EndsWith(account_id, "kiosk-apps.device-local.localhost");

  if (user_exists) {
    UserCryptohomeState& user_state = user_it->second;

    // TODO(b/239422391): Some tests expect that kiosk or gaia keys exist
    // for existing users, but don't set those keys up. Until those tests are
    // fixed, we explicitly add keys here.
    if (is_kiosk) {
      if (!user_state.auth_factors.contains(kCryptohomePublicMountLabel)) {
        LOG(ERROR) << "Listing kiosk key even though it was not set up";

        FakeAuthFactor factor{KioskFactor()};
        user_state.auth_factors.insert(
            {kCryptohomeRecoveryKeyLabel, std::move(factor)});
      };
    } else {
      if (!user_state.auth_factors.contains(kCryptohomeGaiaKeyLabel) &&
          !user_state.auth_factors.contains(kCryptohomeLocalPasswordKeyLabel)) {
        LOG(ERROR) << "Listing GAIA password key even though it was not set up";
        FakeAuthFactor factor{PasswordFactor()};
        user_state.auth_factors.insert(
            {kCryptohomeGaiaKeyLabel, std::move(factor)});
      };
    }

    for (const auto& [label, factor] : user_state.auth_factors) {
      std::optional<user_data_auth::AuthFactor> auth_factor =
          FakeAuthFactorToAuthFactor(label, factor);
      DCHECK(auth_factor);
      *reply.add_auth_factors() = *auth_factor;
      auto* factor_with_status =
          reply.add_configured_auth_factors_with_status();
      BuildAuthFactorWithStatus(auth_factor, factor_with_status);
    }
  }
}

void FakeUserDataAuthClient::ListAuthFactors(
    const ::user_data_auth::ListAuthFactorsRequest& request,
    ListAuthFactorsCallback callback) {
  ::user_data_auth::ListAuthFactorsReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kListAuthFactors>(request);

  if (auto error = TakeOperationError(Operation::kListAuthFactors);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  const auto user_it = users_.find(request.account_id());
  const bool user_exists = user_it != std::end(users_);
  if (!user_exists) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND));
    return;
  }

  const UserCryptohomeState& user_state = user_it->second;
  for (const auto& [label, factor] : user_state.auth_factors) {
    std::optional<user_data_auth::AuthFactor> auth_factor =
        FakeAuthFactorToAuthFactor(label, factor);
    if (auth_factor) {
      *reply.add_configured_auth_factors() = *auth_factor;
      auto* factor_with_status =
          reply.add_configured_auth_factors_with_status();
      BuildAuthFactorWithStatus(auth_factor, factor_with_status);
    } else {
      LOG(WARNING) << "Ignoring auth factor incompatible with AuthFactor API: "
                   << label;
    }
  }

  const std::string& account_id = request.account_id().account_id();
  // See device_local_account.h
  const bool is_kiosk =
      base::EndsWith(account_id, "kiosk-apps.device-local.localhost");

  if (is_kiosk) {
    reply.add_supported_auth_factors(user_data_auth::AUTH_FACTOR_TYPE_KIOSK);
  } else {
    reply.add_supported_auth_factors(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
    if (supports_low_entropy_credentials_) {
      reply.add_supported_auth_factors(user_data_auth::AUTH_FACTOR_TYPE_PIN);
      reply.add_supported_auth_factors(
          user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY);
    }
  }
}

void FakeUserDataAuthClient::PrepareGuestVault(
    const ::user_data_auth::PrepareGuestVaultRequest& request,
    PrepareGuestVaultCallback callback) {
  ::user_data_auth::PrepareGuestVaultReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kPrepareGuestVault>(request);

  if (auto error = TakeOperationError(Operation::kPrepareGuestVault);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  prepare_guest_request_count_++;

  cryptohome::AccountIdentifier account;
  account.set_account_id(kGuestUserName);
  reply.set_sanitized_username(GetStubSanitizedUsername(account));
}

void FakeUserDataAuthClient::PrepareEphemeralVault(
    const ::user_data_auth::PrepareEphemeralVaultRequest& request,
    PrepareEphemeralVaultCallback callback) {
  ::user_data_auth::PrepareEphemeralVaultReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kPrepareEphemeralVault>(request);

  if (auto error = TakeOperationError(Operation::kPrepareEphemeralVault);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  const auto session_it = auth_sessions_.find(request.auth_session_id());
  if (session_it == auth_sessions_.end()) {
    LOG(ERROR) << "AuthSession not found";
    reply.set_sanitized_username(std::string());
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }
  AuthSessionData& auth_session = session_it->second;
  if (!auth_session.ephemeral) {
    LOG(ERROR) << "Non-ephemeral AuthSession used with PrepareEphemeralVault";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }
  cryptohome::AccountIdentifier account = auth_session.account;
  // Ephemeral mount does not require session to be authenticated;
  // It authenticates session instead.
  if (auth_session.authenticated) {
    LOG(ERROR) << "AuthSession is authenticated";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }
  auth_session.authenticated = true;
  auth_session.requested_auth_session_intent =
      user_data_auth::AUTH_INTENT_DECRYPT;
  auth_session.lifetime =
      base::Time::Now() + cryptohome::kAuthsessionInitialLifetime;

  auto user_state = UserCryptohomeState();
  user_state.ephemeral = true;
  const auto [_, was_inserted] =
      users_.insert({auth_session.account, user_state});

  if (!was_inserted) {
    LOG(ERROR) << "User already exists: " << auth_session.account.account_id();
    reply.set_error(
        CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
    return;
  }

  reply.set_sanitized_username(GetStubSanitizedUsername(account));

  reply.mutable_auth_properties()->add_authorized_for(
      auth_session.requested_auth_session_intent);
  reply.mutable_auth_properties()->set_seconds_left(
      cryptohome::kAuthsessionInitialLifetime.InSeconds());
}

void FakeUserDataAuthClient::CreatePersistentUser(
    const ::user_data_auth::CreatePersistentUserRequest& request,
    CreatePersistentUserCallback callback) {
  ::user_data_auth::CreatePersistentUserReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kCreatePersistentUser>(request);

  if (auto error = TakeOperationError(Operation::kCreatePersistentUser);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  const auto session_it = auth_sessions_.find(request.auth_session_id());
  if (session_it == auth_sessions_.end()) {
    LOG(ERROR) << "AuthSession not found";
    reply.set_sanitized_username(std::string());
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }
  AuthSessionData& auth_session = session_it->second;

  if (auth_session.ephemeral) {
    LOG(ERROR) << "Ephemeral AuthSession used with CreatePersistentUser";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }

  const auto [_, was_inserted] =
      users_.insert({auth_session.account, UserCryptohomeState()});

  if (!was_inserted) {
    LOG(ERROR) << "User already exists: " << auth_session.account.account_id();
    reply.set_error(
        CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
    return;
  }

  auth_session.authenticated = true;
  auth_session.requested_auth_session_intent =
      user_data_auth::AUTH_INTENT_DECRYPT;
  auth_session.lifetime =
      base::Time::Now() + cryptohome::kAuthsessionInitialLifetime;

  reply.mutable_auth_properties()->add_authorized_for(
      auth_session.requested_auth_session_intent);
  reply.mutable_auth_properties()->set_seconds_left(
      cryptohome::kAuthsessionInitialLifetime.InSeconds());
}

void FakeUserDataAuthClient::PreparePersistentVault(
    const ::user_data_auth::PreparePersistentVaultRequest& request,
    PreparePersistentVaultCallback callback) {
  ::user_data_auth::PreparePersistentVaultReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kPreparePersistentVault>(request);

  if (auto error = TakeOperationError(Operation::kPreparePersistentVault);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* authenticated_auth_session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);

  if (authenticated_auth_session == nullptr) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  if (authenticated_auth_session->ephemeral) {
    LOG(ERROR) << "Ephemeral AuthSession used with PreparePersistentVault";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
    return;
  }

  const auto user_it = users_.find(authenticated_auth_session->account);
  if (user_it == std::end(users_)) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND));
    return;
  }

  if (request.block_ecryptfs() && user_it->second.home_encryption_method ==
                                      HomeEncryptionMethod::kEcryptfs) {
    if (user_it->second.incomplete_migration) {
      LOG(ERROR) << "Encryption migration required, incomplete migration";
      reply.set_error(CryptohomeErrorCode::
                          CRYPTOHOME_ERROR_MOUNT_PREVIOUS_MIGRATION_INCOMPLETE);
    } else {
      LOG(ERROR) << "Encryption migration required, full migration";
      reply.set_error(
          CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_OLD_ENCRYPTION);
    }
    return;
  }

  reply.set_sanitized_username(
      GetStubSanitizedUsername(authenticated_auth_session->account));
  mounted_user_dirs_.insert(authenticated_auth_session->account.account_id());
}

void FakeUserDataAuthClient::PrepareVaultForMigration(
    const ::user_data_auth::PrepareVaultForMigrationRequest& request,
    PrepareVaultForMigrationCallback callback) {
  ::user_data_auth::PrepareVaultForMigrationReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kPrepareVaultForMigration>(request);

  if (auto error = TakeOperationError(Operation::kPrepareVaultForMigration);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* authenticated_auth_session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);

  if (authenticated_auth_session == nullptr) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  if (!users_.contains(authenticated_auth_session->account)) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND));
    return;
  }
}

void FakeUserDataAuthClient::InvalidateAuthSession(
    const ::user_data_auth::InvalidateAuthSessionRequest& request,
    InvalidateAuthSessionCallback callback) {
  ::user_data_auth::InvalidateAuthSessionReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  auto auth_session = auth_sessions_.find(request.auth_session_id());
  if (auth_session == auth_sessions_.end()) {
    LOG(ERROR) << "AuthSession not found";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }

  auth_sessions_.erase(auth_session);
}

void FakeUserDataAuthClient::ExtendAuthSession(
    const ::user_data_auth::ExtendAuthSessionRequest& request,
    ExtendAuthSessionCallback callback) {
  ::user_data_auth::ExtendAuthSessionReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  auto error = cryptohome::ErrorWrapper::success();
  auto* session_data =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  SetErrorWrapperToReply(reply, error);
  if (session_data) {
    auth_sessions_.find(request.auth_session_id())->second.lifetime =
        base::Time::Now() + base::Seconds(request.extension_duration());
  }
}

void FakeUserDataAuthClient::AddAuthFactor(
    const ::user_data_auth::AddAuthFactorRequest& request,
    AddAuthFactorCallback callback) {
  ::user_data_auth::AddAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kAddAuthFactor>(request);

  if (auto error = TakeOperationError(Operation::kAddAuthFactor);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  if (session == nullptr) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto user_it = users_.find(session->account);
  CHECK(user_it != std::end(users_))
      << "User associated with session does not exist";
  UserCryptohomeState& user_state = user_it->second;

  auto [new_label, new_factor] = AuthFactorWithInputToFakeAuthFactor(
      request.auth_factor(), request.auth_input(), enable_auth_check_);
  CHECK(!user_state.auth_factors.contains(new_label))
      << "Key exists, will not clobber: " << new_label;
  user_state.auth_factors[std::move(new_label)] = std::move(new_factor);
}

void FakeUserDataAuthClient::AuthenticateAuthFactor(
    const ::user_data_auth::AuthenticateAuthFactorRequest& request,
    AuthenticateAuthFactorCallback callback) {
  ::user_data_auth::AuthenticateAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kAuthenticateAuthFactor>(request);

  if (auto error = TakeOperationError(Operation::kAuthenticateAuthFactor);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  const auto session_it = auth_sessions_.find(request.auth_session_id());
  if (session_it == auth_sessions_.end()) {
    LOG(ERROR) << "AuthSession not found";
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }
  auto& session = session_it->second;

  CHECK(!session.authenticated) << "Session is already authenticated";

  const auto user_it = users_.find(session.account);
  DCHECK(user_it != std::end(users_));
  const UserCryptohomeState& user_state = user_it->second;

  std::vector<std::string> auth_factor_labels;
  auth_factor_labels.reserve(request.auth_factor_labels_size());
  for (auto label : request.auth_factor_labels()) {
    auth_factor_labels.push_back(label);
  }

  const ::user_data_auth::AuthInput& auth_input = request.auth_input();

  // Checks that the arity of auth factor labels match the AuthInput type.
  // Legacy fingerprint does not have any auth factor associated.
  // And only sign-in fingerprint allows more than 1 auth factor label.
  if (auth_factor_labels.size() == 0) {
    if (!auth_input.has_legacy_fingerprint_input()) {
      SetErrorWrapperToReply(
          reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                     ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
      return;
    }
  } else if (auth_factor_labels.size() > 1) {
    if (!auth_input.has_fingerprint_input()) {
      SetErrorWrapperToReply(
          reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                     ::user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
      return;
    }
  }

  for (const auto& label : auth_factor_labels) {
    const auto factor_it = user_state.auth_factors.find(label);
    if (factor_it == user_state.auth_factors.end()) {
      LOG(ERROR) << "Factor not found: " << label;
      SetErrorWrapperToReply(
          reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                     ::user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND));
      return;
    }
    const FakeAuthFactor& factor = factor_it->second;

    if (!AuthInputMatchesFakeFactorType(auth_input, factor)) {
      LOG(ERROR) << "Auth input does not match factor type";
      SetErrorWrapperToReply(
          reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                     ::user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND));
      return;
    }

    // Factor-specific verification logic. Will set the result_error variable
    // variable if a check didn't pass.
    cryptohome::ErrorWrapper result_error = cryptohome::ErrorWrapper::success();
    absl::visit(
        Overload<void>(
            [&](const PasswordFactor& password_factor) {
              const auto& password_input = auth_input.password_input();

              if (enable_auth_check_ &&
                  password_input.secret() != password_factor.password) {
                result_error =
                    cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                        ::user_data_auth::
                            CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
                return;
              }
            },
            [&](const PinFactor& pin_factor) {
              const auto& pin_input = auth_input.pin_input();

              if (enable_auth_check_ && pin_input.secret() != pin_factor.pin) {
                result_error =
                    cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                        ::user_data_auth::
                            CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
                return;
              }

              if (pin_factor.locked) {
                result_error =
                    cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                        ::user_data_auth::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK);
                return;
              }
            },
            [&](const RecoveryFactor& recovery) {
              const auto& recovery_input =
                  auth_input.cryptohome_recovery_input();

              if (recovery_input.epoch_response().empty()) {
                LOG(ERROR) << "Missing epoch response";
                result_error =
                    cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                        ::user_data_auth::
                            CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
                return;
              }
              if (recovery_input.recovery_response().empty()) {
                LOG(ERROR) << "Missing recovery response";
                result_error =
                    cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                        ::user_data_auth::
                            CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
                return;
              }
            },
            [&](const KioskFactor& kiosk) {},
            [&](const SmartCardFactor& smart_card) {
              LOG(ERROR) << "Checking smart card key is not implemented yet";
            }),
        factor);

    if (cryptohome::HasError(result_error)) {
      SetErrorWrapperToReply(reply, result_error);
      return;
    }
  }

  session.authenticated = true;
  session.authorized_auth_session_intent.Put(
      session.requested_auth_session_intent);
  session.lifetime =
      base::Time::Now() + cryptohome::kAuthsessionInitialLifetime;
  reply.mutable_auth_properties()->add_authorized_for(
      session.requested_auth_session_intent);
  reply.mutable_auth_properties()->set_seconds_left(
      cryptohome::kAuthsessionInitialLifetime.InSeconds());
}

void FakeUserDataAuthClient::UpdateAuthFactor(
    const ::user_data_auth::UpdateAuthFactorRequest& request,
    UpdateAuthFactorCallback callback) {
  ::user_data_auth::UpdateAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kUpdateAuthFactor>(request);

  if (auto error = TakeOperationError(Operation::kUpdateAuthFactor);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  SetErrorWrapperToReply(reply, error);
  if (session == nullptr) {
    return;
  }

  auto user_it = users_.find(session->account);
  DCHECK(user_it != std::end(users_));
  UserCryptohomeState& user_state = user_it->second;

  // Update the fake auth factor according to the new secret.
  auto [new_label, new_factor] = AuthFactorWithInputToFakeAuthFactor(
      request.auth_factor(), request.auth_input(), enable_auth_check_);
  CHECK_EQ(new_label, request.auth_factor_label());
  CHECK(user_state.auth_factors.contains(new_label))
      << "Key does not exist: " << new_label;
  user_state.auth_factors[std::move(new_label)] = std::move(new_factor);
}

void FakeUserDataAuthClient::UpdateAuthFactorMetadata(
    const ::user_data_auth::UpdateAuthFactorMetadataRequest& request,
    UpdateAuthFactorMetadataCallback callback) {
  ::user_data_auth::UpdateAuthFactorMetadataReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kUpdateAuthFactorMetadata>(request);

  if (auto error = TakeOperationError(Operation::kUpdateAuthFactor);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  SetErrorWrapperToReply(reply, error);
  if (session == nullptr) {
    return;
  }

  auto user_it = users_.find(session->account);
  DCHECK(user_it != std::end(users_));
  UserCryptohomeState& user_state = user_it->second;

  // Check that the factor updated exists. We don't have to modify the stored
  // factor because metadata fields are not recorded in this implementation.
  CHECK(user_state.auth_factors.contains(request.auth_factor_label()))
      << "Key does not exist: " << request.auth_factor_label();
  return;
}

void FakeUserDataAuthClient::ReplaceAuthFactor(
    const ::user_data_auth::ReplaceAuthFactorRequest& request,
    ReplaceAuthFactorCallback callback) {
  ::user_data_auth::ReplaceAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kReplaceAuthFactor>(request);

  if (auto error = TakeOperationError(Operation::kReplaceAuthFactor);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  auto error = cryptohome::ErrorWrapper::success();
  auto* session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  SetErrorWrapperToReply(reply, error);
  if (session == nullptr) {
    return;
  }

  auto user_it = users_.find(session->account);
  DCHECK(user_it != std::end(users_));
  UserCryptohomeState& user_state = user_it->second;

  const std::string& old_label = request.auth_factor_label();
  DCHECK(!old_label.empty());
  CHECK(user_state.auth_factors.contains(old_label))
      << "Key does not exist: " << old_label;

  auto [new_label, new_factor] = AuthFactorWithInputToFakeAuthFactor(
      request.auth_factor(), request.auth_input(), enable_auth_check_);
  CHECK(!user_state.auth_factors.contains(new_label))
      << "Key already exists: " << new_label;

  // Remove the fake old auth factor and replace with the new one.
  bool erased = user_state.auth_factors.erase(old_label) > 0;
  if (erased) {
    user_state.auth_factors[std::move(new_label)] = std::move(new_factor);
  } else {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_KEY_NOT_FOUND));
  }
}

void FakeUserDataAuthClient::RemoveAuthFactor(
    const ::user_data_auth::RemoveAuthFactorRequest& request,
    RemoveAuthFactorCallback callback) {
  ::user_data_auth::RemoveAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  auto error = cryptohome::ErrorWrapper::success();
  auto* session =
      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
  SetErrorWrapperToReply(reply, error);
  if (session == nullptr) {
    return;
  }
  auto user_it = users_.find(session->account);
  DCHECK(user_it != std::end(users_));
  UserCryptohomeState& user_state = user_it->second;

  const std::string& label = request.auth_factor_label();
  DCHECK(!label.empty());
  bool erased = user_state.auth_factors.erase(label) > 0;

  if (!erased) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_KEY_NOT_FOUND));
  }
}

void FakeUserDataAuthClient::GetAuthFactorExtendedInfo(
    const ::user_data_auth::GetAuthFactorExtendedInfoRequest& request,
    GetAuthFactorExtendedInfoCallback callback) {
  ::user_data_auth::GetAuthFactorExtendedInfoReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
}

void FakeUserDataAuthClient::GetAuthSessionStatus(
    const ::user_data_auth::GetAuthSessionStatusRequest& request,
    GetAuthSessionStatusCallback callback) {
  ::user_data_auth::GetAuthSessionStatusReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  const std::string auth_session_id = request.auth_session_id();
  auto auth_session = auth_sessions_.find(auth_session_id);
  // Check if the token refers to a valid AuthSession.
  if (auth_session == auth_sessions_.end()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }

  base::TimeDelta time_left = auth_session->second.lifetime - base::Time::Now();
  reply.mutable_auth_properties()->add_authorized_for(
      auth_session->second.requested_auth_session_intent);
  reply.mutable_auth_properties()->set_seconds_left(time_left.InSeconds());
}

void FakeUserDataAuthClient::PrepareAuthFactor(
    const ::user_data_auth::PrepareAuthFactorRequest& request,
    PrepareAuthFactorCallback callback) {
  ::user_data_auth::PrepareAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  const std::string auth_session_id = request.auth_session_id();
  auto auth_session = auth_sessions_.find(auth_session_id);
  // Check if the token refers to a valid AuthSession.
  if (auth_session == auth_sessions_.end()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }

  switch (request.auth_factor_type()) {
    case user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY:
      SetErrorWrapperToReply(
          reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                     CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET));
      reply.mutable_prepare_output()
          ->mutable_cryptohome_recovery_output()
          ->set_recovery_request("fake-recovery-request");
      break;
    case user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT:
      CHECK(!fingerprint_observers_.empty())
          << "Add relevant observer before calling PrepareAuthFactor";

      CHECK(!auth_session->second.is_listening_for_fingerprint_events)
          << "Duplicate call to PrepareAuthFactor";
      auth_session->second.is_listening_for_fingerprint_events = true;
      break;
    default:
      LOG(FATAL) << "Only Recovery and Legacy FP support PrepareAuthFactor in "
                    "FakeUDAC";
  }
}

void FakeUserDataAuthClient::TerminateAuthFactor(
    const ::user_data_auth::TerminateAuthFactorRequest& request,
    TerminateAuthFactorCallback callback) {
  ::user_data_auth::TerminateAuthFactorReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));

  const std::string auth_session_id = request.auth_session_id();
  auto auth_session = auth_sessions_.find(auth_session_id);
  // Check if the token refers to a valid AuthSession.
  if (auth_session == auth_sessions_.end()) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN));
    return;
  }

  CHECK_EQ(request.auth_factor_type(),
           user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT)
      << "Only Legacy FP is supported in FakeUDAC";

  CHECK(auth_session->second.is_listening_for_fingerprint_events)
      << "Call to TerminateAuthFactor without prior PrepareAuthFactor";
  auth_session->second.is_listening_for_fingerprint_events = false;
}

void FakeUserDataAuthClient::GetArcDiskFeatures(
    const ::user_data_auth::GetArcDiskFeaturesRequest& request,
    GetArcDiskFeaturesCallback callback) {
  ::user_data_auth::GetArcDiskFeaturesReply reply;
  reply.set_quota_supported(arc_quota_supported_);
  std::move(callback).Run(std::move(reply));
}

void FakeUserDataAuthClient::WaitForServiceToBeAvailable(
    chromeos::WaitForServiceToBeAvailableCallback callback) {
  if (service_is_available_ || service_reported_not_available_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), service_is_available_));
  } else {
    pending_wait_for_service_to_be_available_callbacks_.push_back(
        std::move(callback));
  }
}

void FakeUserDataAuthClient::RunPendingWaitForServiceToBeAvailableCallbacks() {
  std::vector<chromeos::WaitForServiceToBeAvailableCallback> callbacks;
  callbacks.swap(pending_wait_for_service_to_be_available_callbacks_);
  for (auto& callback : callbacks) {
    std::move(callback).Run(false);
  }
}

FakeUserDataAuthClient::AuthResult
FakeUserDataAuthClient::AuthenticateViaAuthFactors(
    const cryptohome::AccountIdentifier& account_id,
    const std::string& factor_label,
    const std::string& secret,
    bool wildcard_allowed,
    std::string* matched_factor_label) const {
  if (!enable_auth_check_) {
    return AuthResult::kAuthSuccess;
  }

  const auto user_it = users_.find(account_id);
  if (user_it == std::end(users_)) {
    return AuthResult::kUserNotFound;
  }
  const UserCryptohomeState& user_state = user_it->second;

  if (wildcard_allowed && factor_label.empty()) {
    // Do a wildcard match (it's only used for legacy APIs): try the secret
    // against every credential.
    for (const auto& [candidate_label, candidate_factor] :
         user_state.auth_factors) {
      if (CheckCredentialsViaAuthFactor(candidate_factor, secret)) {
        if (matched_factor_label) {
          *matched_factor_label = candidate_label;
        }
        return AuthResult::kAuthSuccess;
      }
    }
    // It's not well-defined which error is returned on a failed wildcard
    // authentication, but we follow what the real cryptohome does (at least in
    // CheckKey).
    return AuthResult::kAuthFailed;
  }

  const auto factor_it = user_state.auth_factors.find(factor_label);
  if (factor_it == std::end(user_state.auth_factors)) {
    return AuthResult::kFactorNotFound;
  }
  const auto& [label, factor] = *factor_it;
  if (!CheckCredentialsViaAuthFactor(factor, secret)) {
    return AuthResult::kAuthFailed;
  }
  if (matched_factor_label) {
    *matched_factor_label = label;
  }
  return AuthResult::kAuthSuccess;
}

void FakeUserDataAuthClient::SetNextOperationError(
    FakeUserDataAuthClient::Operation operation,
    cryptohome::ErrorWrapper error) {
  operation_errors_.insert_or_assign(operation, std::move(error));
  // operation_errors_[operation] = std::move(error);
}

cryptohome::ErrorWrapper FakeUserDataAuthClient::TakeOperationError(
    Operation operation) {
  const auto op_error = operation_errors_.find(operation);
  if (op_error == std::end(operation_errors_)) {
    return cryptohome::ErrorWrapper::success();
  }
  cryptohome::ErrorWrapper result = op_error->second;
  operation_errors_.erase(op_error);
  return result;
}

void FakeUserDataAuthClient::OnDircryptoMigrationProgressUpdated() {
  dircrypto_migration_progress_++;

  if (dircrypto_migration_progress_ >= kDircryptoMigrationMaxProgress) {
    NotifyDircryptoMigrationProgress(
        ::user_data_auth::DircryptoMigrationStatus::DIRCRYPTO_MIGRATION_SUCCESS,
        dircrypto_migration_progress_, kDircryptoMigrationMaxProgress);
    const auto user_it = users_.find(
        GetLastRequest<Operation::kStartMigrateToDircrypto>().account_id());
    DCHECK(user_it != std::end(users_))
        << "User for dircrypto migration does not exist";

    UserCryptohomeState& user_state = user_it->second;
    user_state.home_encryption_method = HomeEncryptionMethod::kDirCrypto;
    dircrypto_migration_progress_timer_.Stop();
    return;
  }
  NotifyDircryptoMigrationProgress(::user_data_auth::DircryptoMigrationStatus::
                                       DIRCRYPTO_MIGRATION_IN_PROGRESS,
                                   dircrypto_migration_progress_,
                                   kDircryptoMigrationMaxProgress);
}

void FakeUserDataAuthClient::NotifyLowDiskSpace(uint64_t disk_free_bytes) {
  ::user_data_auth::LowDiskSpace status;
  status.set_disk_free_bytes(disk_free_bytes);
  for (auto& observer : observer_list_) {
    observer.LowDiskSpace(status);
  }
}

void FakeUserDataAuthClient::NotifyDircryptoMigrationProgress(
    ::user_data_auth::DircryptoMigrationStatus status,
    uint64_t current,
    uint64_t total) {
  ::user_data_auth::DircryptoMigrationProgress progress;
  progress.set_status(status);
  progress.set_current_bytes(current);
  progress.set_total_bytes(total);
  for (auto& observer : observer_list_) {
    observer.DircryptoMigrationProgress(progress);
  }
}

std::optional<base::FilePath> FakeUserDataAuthClient::GetUserProfileDir(
    const cryptohome::AccountIdentifier& account_id) const {
  if (!user_data_dir_.has_value()) {
    return std::nullopt;
  }

  std::string user_dir_base_name =
      kUserDataDirNamePrefix + account_id.account_id() + kUserDataDirNameSuffix;
  const base::FilePath profile_dir =
      user_data_dir_->Append(std::move(user_dir_base_name));
  return profile_dir;
}

const FakeUserDataAuthClient::AuthSessionData*
FakeUserDataAuthClient::GetAuthenticatedAuthSession(
    const std::string& auth_session_id,
    cryptohome::ErrorWrapper* error) const {
  auto auth_session = auth_sessions_.find(auth_session_id);

  // Check if the token refers to a valid AuthSession.
  if (auth_session == auth_sessions_.end()) {
    LOG(ERROR) << "AuthSession not found";
    *error = cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
        CryptohomeErrorCode::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN);
    return nullptr;
  }

  // Check if the AuthSession is properly authenticated.
  if (!auth_session->second.authenticated) {
    LOG(ERROR) << "AuthSession is not authenticated";
    *error = cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
        CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
    return nullptr;
  }

  return &auth_session->second;
}

void FakeUserDataAuthClient::SetUserDataDir(base::FilePath path) {
  CHECK(!user_data_dir_.has_value());
  user_data_dir_ = std::move(path);

  std::string pattern = kUserDataDirNamePrefix + "*" + kUserDataDirNameSuffix;
  base::FileEnumerator e(*user_data_dir_, /*recursive=*/false,
                         base::FileEnumerator::DIRECTORIES, std::move(pattern));
  for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
    const base::FilePath base_name = name.BaseName();
    DCHECK(base::StartsWith(base_name.value(), kUserDataDirNamePrefix));
    DCHECK(base::EndsWith(base_name.value(), kUserDataDirNameSuffix));

    // Remove kUserDataDirNamePrefix from front and kUserDataDirNameSuffix from
    // end to obtain account id.
    std::string account_id_str(
        base_name.value().begin() + kUserDataDirNamePrefix.size(),
        base_name.value().end() - kUserDataDirNameSuffix.size());

    cryptohome::AccountIdentifier account_id;
    account_id.set_account_id(std::move(account_id_str));

    // This does intentionally not override existing entries.
    users_.insert({std::move(account_id), UserCryptohomeState()});
  }
}

void FakeUserDataAuthClient::GetRecoverableKeyStores(
    const ::user_data_auth::GetRecoverableKeyStoresRequest& request,
    GetRecoverableKeyStoresCallback callback) {
  ::user_data_auth::GetRecoverableKeyStoresReply reply;
  ReplyOnReturn auto_reply(&reply, std::move(callback));
  RememberRequest<Operation::kGetRecoverableKeyStores>(request);

  if (auto error = TakeOperationError(Operation::kGetRecoverableKeyStores);
      cryptohome::HasError(error)) {
    SetErrorWrapperToReply(reply, error);
    return;
  }

  const auto user_it = users_.find(request.account_id());
  const bool user_exists = user_it != std::end(users_);
  if (!user_exists) {
    SetErrorWrapperToReply(
        reply, cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
                   CryptohomeErrorCode::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND));
    return;
  }

  const UserCryptohomeState& user_state = user_it->second;
  for (const auto& [label, factor] : user_state.auth_factors) {
    std::optional<cryptohome::RecoverableKeyStore> store =
        FakeAuthFactorToRecoverableKeyStore(factor);
    if (store) {
      *reply.add_key_stores() = std::move(*store);
    }
  }
}

void FakeUserDataAuthClient::SetUserDataStorageWriteEnabled(
    const ::user_data_auth::SetUserDataStorageWriteEnabledRequest& request,
    SetUserDataStorageWriteEnabledCallback callback) {
  ::user_data_auth::SetUserDataStorageWriteEnabledReply reply;
  std::move(callback).Run(std::move(reply));
}

}  // namespace ash