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

#include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_screen.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "ash/constants/ash_login_pref_names.h"
#include "ash/public/cpp/reauth_reason.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/syslog_logging.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "chrome/browser/ash/login/oobe_screen.h"
#include "chrome/browser/ash/login/reauth_stats.h"
#include "chrome/browser/ash/login/screens/base_screen.h"
#include "chrome/browser/ash/login/wizard_context.h"
#include "chrome/browser/ui/webui/ash/login/cryptohome_recovery_screen_handler.h"
#include "chromeos/ash/components/cryptohome/auth_factor.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/auth_factors_configuration.h"
#include "chromeos/ash/components/login/auth/public/authentication_error.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "chromeos/ash/components/login/auth/recovery/cryptohome_recovery_performer.h"
#include "chromeos/ash/components/osauth/public/auth_session_storage.h"
#include "chromeos/ash/components/osauth/public/common_types.h"
#include "chromeos/ash/services/auth_factor_config/auth_factor_config_utils.h"
#include "components/device_event_log/device_event_log.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

namespace {

constexpr char kUserActionReauth[] = "reauth";

}  // namespace

namespace ash {

// static
std::string CryptohomeRecoveryScreen::GetResultString(Result result) {
  // LINT.IfChange(UsageMetrics)
  switch (result) {
    case Result::kGaiaLogin:
      return "GaiaLogin";
    case Result::kAuthenticated:
      return "Authenticated";
    case Result::kError:
      return "Error";
    case Result::kFallbackLocal:
      return "FallbackLocal";
    case Result::kFallbackOnline:
      return "FallbackOnline";
  }
  // LINT.ThenChange(//tools/metrics/histograms/metadata/oobe/histograms.xml)
}

CryptohomeRecoveryScreen::CryptohomeRecoveryScreen(
    PrefService& local_state,
    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
    base::WeakPtr<CryptohomeRecoveryScreenView> view,
    const ScreenExitCallback& exit_callback)
    : BaseScreen(CryptohomeRecoveryScreenView::kScreenId,
                 OobeScreenPriority::DEFAULT),
      local_state_(local_state),
      shared_url_loader_factory_(std::move(shared_url_loader_factory)),
      auth_factor_editor_(UserDataAuthClient::Get()),
      view_(std::move(view)),
      exit_callback_(exit_callback) {
  CHECK(shared_url_loader_factory_);
}

CryptohomeRecoveryScreen::~CryptohomeRecoveryScreen() = default;

void CryptohomeRecoveryScreen::ShowImpl() {
  if (!view_)
    return;

  CHECK(context()->user_context);

  if (context()->ShouldTriggerAutoWipe(local_state_.get())) {
    LOGIN_LOG(EVENT)
        << "AutoWipe behavior active: skipping cryptohome recovery";
    SYSLOG(INFO)
        << "(LOGIN) AutoWipe behavior active: skipping cryptohome recovery";
    exit_callback_.Run(Result::kFallbackOnline);
    return;
  }

  auth_factor_editor_.GetAuthFactorsConfiguration(
      std::move(context()->user_context),
      base::BindOnce(&CryptohomeRecoveryScreen::OnGetAuthFactorsConfiguration,
                     weak_ptr_factory_.GetWeakPtr()));

  view_->Show();
}

void CryptohomeRecoveryScreen::HideImpl() {}

void CryptohomeRecoveryScreen::OnUserAction(const base::ListValue& args) {
  const std::string& action_id = args[0].GetString();
  if (action_id == kUserActionReauth) {
    exit_callback_.Run(Result::kGaiaLogin);
  } else {
    BaseScreen::OnUserAction(args);
  }
}

void CryptohomeRecoveryScreen::OnGetAuthFactorsConfiguration(
    std::unique_ptr<UserContext> user_context,
    std::optional<AuthenticationError> error) {
  if (error.has_value()) {
    LOG(ERROR) << "Failed to get auth factors configuration, code "
               << error->get_cryptohome_error();
    context()->user_context = std::move(user_context);
    context()->osauth_error = WizardContext::OSAuthErrorKind::kFatal;
    exit_callback_.Run(Result::kError);
    return;
  }

  const auto& config = user_context->GetAuthFactorsConfiguration();
  bool has_online_password = false;
  bool has_local_password = false;
  if (config.HasConfiguredFactor(cryptohome::AuthFactorType::kPassword)) {
    has_online_password = auth::IsGaiaPassword(
        *config.FindFactorByType(cryptohome::AuthFactorType::kPassword));
    has_local_password = auth::IsLocalPassword(
        *config.FindFactorByType(cryptohome::AuthFactorType::kPassword));
  }

  bool is_configured =
      config.HasConfiguredFactor(cryptohome::AuthFactorType::kRecovery);

  if (is_configured) {
    if (user_context->GetReauthProofToken().empty()) {
      auto account_id = user_context->GetAccountId();
      context()->user_context = std::move(user_context);
      if (was_reauth_proof_token_missing_) {
        LOG(ERROR)
            << "Reauth proof token is still missing after the second attempt";
        context()->osauth_error = WizardContext::OSAuthErrorKind::kFatal;
        exit_callback_.Run(Result::kError);
        return;
      } else {
        LOG(WARNING) << "Reauth proof token is not present";
        was_reauth_proof_token_missing_ = true;
        RecordReauthReason(local_state_.get(), account_id,
                           ReauthReason::kCryptohomeRecovery);
        view_->ShowReauthNotification();
        return;
      }
    }
    CHECK(user_context->HasAuthFactorsConfiguration());
    if (!has_online_password && !has_local_password) {
      LOG(ERROR) << "Contuining Recovery with no passwords";
    }
    recovery_performer_ = std::make_unique<CryptohomeRecoveryPerformer>(
        UserDataAuthClient::Get(), shared_url_loader_factory_);
    recovery_performer_->AuthenticateWithRecovery(
        std::move(user_context),
        base::BindOnce(&CryptohomeRecoveryScreen::OnAuthenticateWithRecovery,
                       weak_ptr_factory_.GetWeakPtr()));
  } else {
    CHECK(user_context->HasAuthFactorsConfiguration());
    const bool has_pin =
        config.HasConfiguredFactor(cryptohome::AuthFactorType::kPin);
    const bool has_smart_card =
        config.HasConfiguredFactor(cryptohome::AuthFactorType::kSmartCard);
    CHECK(!has_smart_card) << "Recovery for smart card users is not supported!";

    context()->user_context = std::move(user_context);
    if (has_online_password) {
      CHECK(!has_local_password);
      exit_callback_.Run(Result::kFallbackOnline);
    } else {
      CHECK(has_local_password || has_pin);
      exit_callback_.Run(Result::kFallbackLocal);
    }
  }
}

void CryptohomeRecoveryScreen::OnAuthenticateWithRecovery(
    std::unique_ptr<UserContext> user_context,
    std::optional<AuthenticationError> error) {
  if (error.has_value()) {
    LOG(ERROR) << "Failed to authenticate with recovery, "
               << error->ToDebugString();
    context()->user_context = std::move(user_context);
    context()->osauth_error =
        WizardContext::OSAuthErrorKind::kRecoveryAuthenticationFailed;
    exit_callback_.Run(Result::kError);
    return;
  }

  // The user just authenticated with recovery factor and therefore we want to
  // rotate the recovery id after the user directory is mounted.
  user_context->SetGenerateFreshRecoveryId(true);
  auth_factor_editor_.RotateRecoveryFactor(
      std::move(user_context),
      /*ensure_fresh_recovery_id=*/false,
      base::BindOnce(&CryptohomeRecoveryScreen::OnRotateRecoveryFactor,
                     weak_ptr_factory_.GetWeakPtr()));
}

void CryptohomeRecoveryScreen::OnRotateRecoveryFactor(
    std::unique_ptr<UserContext> user_context,
    std::optional<AuthenticationError> error) {
  if (error.has_value()) {
    LOG(ERROR) << "Failed to rotate recovery factor, code "
               << error->get_cryptohome_error();
    context()->extra_factors_token =
        ash::AuthSessionStorage::Get()->Store(std::move(user_context));
    context()->osauth_error =
        WizardContext::OSAuthErrorKind::kRecoveryRotationFailed;
    exit_callback_.Run(Result::kError);
    return;
  }

  // Get AuthFactorsConfiguration again, as it was cleared after
  // rotation.
  auth_factor_editor_.GetAuthFactorsConfiguration(
      std::move(user_context),
      base::BindOnce(&CryptohomeRecoveryScreen::OnRefreshFactorsConfiguration,
                     weak_ptr_factory_.GetWeakPtr()));
}

void CryptohomeRecoveryScreen::OnRefreshFactorsConfiguration(
    std::unique_ptr<UserContext> user_context,
    std::optional<AuthenticationError> error) {
  if (error.has_value()) {
    LOG(ERROR) << "Failed to get auth factors configuration, code "
               << error->get_cryptohome_error();
    context()->user_context = std::move(user_context);
    context()->osauth_error = WizardContext::OSAuthErrorKind::kFatal;
    exit_callback_.Run(Result::kError);
    return;
  }
  context()->extra_factors_token =
      ash::AuthSessionStorage::Get()->Store(std::move(user_context));
  exit_callback_.Run(Result::kAuthenticated);
}

}  // namespace ash
