// Copyright 2013 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/login_manager_test.h"

#include <string>

#include "ash/constants/ash_switches.h"
#include "ash/metrics/login_unlock_throughput_recorder.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
#include "chrome/browser/ash/login/test/profile_prepared_waiter.h"
#include "chrome/browser/ash/login/test/user_auth_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/ash/login/login_display_host_webui.h"
#include "chrome/test/base/fake_gaia_mixin.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/login/auth/public/key.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/account_id/account_id.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

LoginManagerTest::LoginManagerTest() {
  set_exit_when_last_browser_closes(false);
}

LoginManagerTest::~LoginManagerTest() = default;

void LoginManagerTest::SetUpCommandLine(base::CommandLine* command_line) {
  command_line->AppendSwitch(switches::kLoginManager);
  command_line->AppendSwitch(switches::kForceLoginManagerInTests);
  command_line->AppendSwitch(
      switches::kDisableOOBEChromeVoxHintTimerForTesting);

  MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
}

void LoginManagerTest::SetUpOnMainThread() {
  host_resolver()->AddRule("*", "127.0.0.1");

  test::UserSessionManagerTestApi session_manager_test_api(
      UserSessionManager::GetInstance());
  session_manager_test_api.SetShouldLaunchBrowserInTests(
      should_launch_browser_);
  session_manager_test_api.SetShouldObtainTokenHandleInTests(false);

  MixinBasedInProcessBrowserTest::SetUpOnMainThread();
}

void LoginManagerTest::RegisterUser(const AccountId& account_id) {
  ScopedListPrefUpdate users_pref(g_browser_process->local_state(),
                                  "LoggedInUsers");
  std::string email_value(account_id.GetUserEmail());
  if (!users_pref.Get().contains(email_value)) {
    users_pref->Append(email_value);
  }
  if (user_manager::UserManager::IsInitialized()) {
    user_manager::KnownUser(g_browser_process->local_state())
        .SaveKnownUser(account_id);
    user_manager::UserManager::Get()->SaveUserOAuthStatus(
        account_id, user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
  }
}

constexpr char LoginManagerTest::kPassword[] = "password";

constexpr char LoginManagerTest::kLocalPassword[] = "local-password";

UserContext LoginManagerTest::CreateUserContext(const AccountId& account_id,
                                                const std::string& password) {
  UserContext user_context(user_manager::UserType::kRegular, account_id);
  user_context.SetKey(Key(password));
  user_context.SetGaiaPassword(GaiaPassword(password));
  user_context.SetPasswordKey(Key(password));
  if (account_id.GetUserEmail() == FakeGaiaMixin::kEnterpriseUser1) {
    user_context.SetRefreshToken(FakeGaiaMixin::kTestRefreshToken1);
  } else if (account_id.GetUserEmail() == FakeGaiaMixin::kEnterpriseUser2) {
    user_context.SetRefreshToken(FakeGaiaMixin::kTestRefreshToken2);
  }
  return user_context;
}

UserContext LoginManagerTest::CreateUserContextWithLocalPassword(
    const AccountId& account_id,
    const std::string& password) {
  UserContext user_context(user_manager::UserType::kRegular, account_id);
  user_context.SetKey(Key(password));
  user_context.SetLocalPasswordInput(LocalPasswordInput(password));
  user_context.SetPasswordKey(Key(password));
  return user_context;
}

UserContext LoginManagerTest::CreateUserContextWithPin(
    const AccountId& account_id,
    const std::string& pin) {
  UserContext user_context(user_manager::UserType::kRegular, account_id);
  user_context.SetKey(Key(pin));
  user_context.SetIsUsingPin(true);
  return user_context;
}

void LoginManagerTest::SetExpectedCredentials(const UserContext& user_context) {
  test::UserSessionManagerTestApi session_manager_test_api(
      UserSessionManager::GetInstance());
  session_manager_test_api.InjectStubUserContext(user_context);
}

bool LoginManagerTest::TryToLogin(const UserContext& user_context) {
  if (!AddUserToSession(user_context))
    return false;
  if (const user_manager::User* active_user =
          user_manager::UserManager::Get()->GetActiveUser())
    return active_user->GetAccountId() == user_context.GetAccountId();
  return false;
}

bool LoginManagerTest::AddUserToSession(const UserContext& user_context) {
  ExistingUserController* controller =
      ExistingUserController::current_controller();
  if (!controller) {
    ADD_FAILURE();
    return false;
  }
  test::ProfilePreparedWaiter profile_prepared(user_context.GetAccountId());
  controller->Login(user_context, SigninSpecifics());
  profile_prepared.Wait();
  const user_manager::UserList& logged_users =
      user_manager::UserManager::Get()->GetLoggedInUsers();
  for (user_manager::UserList::const_iterator it = logged_users.begin();
       it != logged_users.end(); ++it) {
    if ((*it)->GetAccountId() == user_context.GetAccountId())
      return true;
  }
  return false;
}

void LoginManagerTest::LoginUser(const AccountId& account_id) {
  const UserContext user_context = CreateUserContext(account_id, kPassword);
  SetExpectedCredentials(user_context);
  EXPECT_TRUE(TryToLogin(user_context));
}

void LoginManagerTest::LoginUserWithLocalPassword(const AccountId& account_id) {
  const UserContext user_context =
      CreateUserContextWithLocalPassword(account_id, kLocalPassword);
  SetExpectedCredentials(user_context);
  EXPECT_TRUE(TryToLogin(user_context));
}

void LoginManagerTest::LoginUserWithPin(const AccountId& account_id) {
  const UserContext user_context =
      CreateUserContextWithPin(account_id, test::kAuthPin);
  SetExpectedCredentials(user_context);
  EXPECT_TRUE(TryToLogin(user_context));
}

void LoginManagerTest::AddUser(const AccountId& account_id) {
  const UserContext user_context = CreateUserContext(account_id, kPassword);
  SetExpectedCredentials(user_context);
  EXPECT_TRUE(AddUserToSession(user_context));
}

void LoginManagerTest::LoginUserWithDbusClient(const AccountId& account_id,
                                               const std::string& password) {
  const UserContext user_context = CreateUserContext(account_id, password);
  EXPECT_TRUE(TryToLogin(user_context));
}

void LoginManagerTest::AddUserWithDbusClient(const AccountId& account_id,
                                             const std::string& password) {
  const UserContext user_context = CreateUserContext(account_id, password);
  EXPECT_TRUE(AddUserToSession(user_context));
}

void LoginManagerTest::SetExpectedCredentialsWithDbusClient(
    const AccountId& account_id,
    const std::string& password) {
  auto* test_api = FakeUserDataAuthClient::TestApi::Get();
  test_api->set_enable_auth_check(true);

  const auto cryptohome_id =
      cryptohome::CreateAccountIdentifierFromAccountId(account_id);
  ash::Key key{password};
  key.Transform(ash::Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
                ash::SystemSaltGetter::ConvertRawSaltToHexString(
                    ash::FakeCryptohomeMiscClient::GetStubSystemSalt()));

  user_data_auth::AuthFactor auth_factor;
  user_data_auth::AuthInput auth_input;

  auth_factor.set_label(ash::kCryptohomeGaiaKeyLabel);
  auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);

  auth_input.mutable_password_input()->set_secret(key.GetSecret());

  test_api->AddExistingUser(cryptohome_id);
  test_api->AddAuthFactor(cryptohome_id, auth_factor, auth_input);
}

}  // namespace ash
