// Copyright 2019 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/kerberos/kerberos_credentials_manager.h"

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "ash/constants/ash_pref_names.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/kerberos/kerberos_files_handler.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/global_features.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/dbus/kerberos/kerberos_client.h"
#include "chromeos/ash/components/dbus/kerberos/kerberos_service.pb.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

constexpr char kProfileEmail[] = "gaia_user@example.com";
constexpr char kPrincipal[] = "kerbeROS_user@examPLE.com";
constexpr char kNormalizedPrincipal[] = "kerberos_user@EXAMPLE.COM";
constexpr char kOtherPrincipal[] = "icebear_cloud@example.com";
constexpr char kNormalizedOtherPrincipal[] = "icebear_cloud@EXAMPLE.COM";
constexpr char kYetAnotherPrincipal[] = "yet_another_user@example.com";
constexpr char kBadPrincipal1[] = "";
constexpr char kBadPrincipal2[] = "kerbeROS_user";
constexpr char kBadPrincipal3[] = "kerbeROS_user@";
constexpr char kBadPrincipal4[] = "@examPLE.com";
constexpr char kBadPrincipal5[] = "kerbeROS@user@examPLE.com";
constexpr char kBadManagedPrincipal[] = "${LOGIN_ID@examPLE.com";
constexpr char kPassword[] = "m1sst1ped>_<";
constexpr char kInvalidPassword[] = "";
constexpr char kConfig[] = "[libdefaults]";
constexpr char kInvalidConfig[] = "[libdefaults]\n  allow_weak_crypto = true";

const bool kUnmanaged = false;
const bool kManaged = true;

const bool kDontRememberPassword = false;
const bool kRememberPassword = true;

const bool kDontAllowExisting = false;
const bool kAllowExisting = true;

const bool kDontSaveLoginPassword = false;
const bool kSaveLoginPassword = true;

const int kNoNotification = 0;
const int kOneNotification = 1;
const int kTwoNotifications = 2;

const int kNoAccount = 0;
const int kOneAccount = 1;
const int kTwoAccounts = 2;
const int kThreeAccounts = 3;

const int kOneFailure = 1;
const int kThreeFailures = 3;
const int kLotsOfFailures = 1000000;

// Account keys for the kerberos.accounts pref.
constexpr char kKeyPrincipal[] = "principal";
constexpr char kKeyPassword[] = "password";
constexpr char kKeyRememberPasswordFromPolicy[] =
    "remember_password_from_policy";
constexpr char kKeyKrb5Conf[] = "krb5conf";

// Password placeholder.
constexpr char kLoginPasswordPlaceholder[] = "${PASSWORD}";

// A long time delta, used to fast forward the task environment until all
// pending operations are completed. This value should be equal to the maximum
// time to delay requests on |kBackoffPolicyForManagedAccounts|.
const base::TimeDelta kLongTimeDelay = base::Minutes(10);

// Fake observer used to test notifications sent by KerberosCredentialsManager
// on accounts changes.
class FakeKerberosCredentialsManagerObserver
    : public KerberosCredentialsManager::Observer {
 public:
  FakeKerberosCredentialsManagerObserver() = default;
  ~FakeKerberosCredentialsManagerObserver() override = default;

  int notifications_count() const { return notifications_count_; }

  int accounts_count_at_last_notification() const {
    return accounts_count_at_last_notification_;
  }

  // KerberosCredentialsManager::Observer:
  void OnAccountsChanged() override {
    notifications_count_++;
    accounts_count_at_last_notification_ =
        KerberosClient::Get()->GetTestInterface()->GetNumberOfAccounts();
  }

  void Reset() {
    notifications_count_ = 0;
    accounts_count_at_last_notification_ = 0;
  }

 private:
  // Registers how many times the observer has been notified of account changes.
  int notifications_count_ = 0;
  // Registers the number of accounts saved before the most recent
  // OnAccountsChanged() call.
  int accounts_count_at_last_notification_ = 0;
};

class MockKerberosFilesHandler : public KerberosFilesHandler {
 public:
  MockKerberosFilesHandler(PrefService& local_state,
                           base::RepeatingClosure get_kerberos_files)
      : KerberosFilesHandler(local_state, get_kerberos_files) {}

  ~MockKerberosFilesHandler() override = default;

  MOCK_METHOD(void, DeleteFiles, ());
};

}  // namespace

class KerberosCredentialsManagerTest : public testing::Test {
 public:
  using Account = kerberos::Account;
  using Accounts = std::vector<Account>;

  KerberosCredentialsManagerTest() {
    SessionManagerClient::InitializeFakeInMemory();
    KerberosClient::InitializeFake();
    client_test_interface()->SetTaskDelay(base::TimeDelta());

    TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
        test_url_loader_factory_.GetSafeWeakWrapper());

    user_session_manager_ = std::make_unique<UserSessionManager>(
        TestingBrowserProcess::GetGlobal()->local_state(),
        TestingBrowserProcess::GetGlobal()
            ->GetFeatures()
            ->application_locale_storage(),
        TestingBrowserProcess::GetGlobal()->shared_url_loader_factory(),
        TestingBrowserProcess::GetGlobal()
            ->platform_part()
            ->browser_policy_connector_ash(),
        TestingBrowserProcess::GetGlobal()
            ->platform_part()
            ->component_manager_ash());

    user_manager_->AddUser(AccountId::FromUserEmail(kProfileEmail));

    // Setting the login password for the KerberosAccounts policy tests.
    UserContext* user_context =
        user_session_manager_->mutable_user_context_for_testing();
    user_context->SetPasswordKey(Key(kPassword));
    user_session_manager_->set_start_session_type_for_testing(
        UserSessionManager::StartSessionType::kPrimary);

    TestingProfile::Builder profile_builder;
    profile_builder.SetProfileName(kProfileEmail);
    profile_ = profile_builder.Build();

    mgr_ = std::make_unique<KerberosCredentialsManager>(
        TestingBrowserProcess::GetGlobal()->local_state(), profile_.get());

    mgr_->AddObserver(&observer_);
  }

  KerberosCredentialsManagerTest(const KerberosCredentialsManagerTest&) =
      delete;
  KerberosCredentialsManagerTest& operator=(
      const KerberosCredentialsManagerTest&) = delete;

  ~KerberosCredentialsManagerTest() override {
    mgr_->RemoveObserver(&observer_);
    mgr_.reset();
    user_session_manager_->Shutdown();
    profile_.reset();
    user_session_manager_.reset();
    TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(nullptr);

    KerberosClient::Shutdown();
    SessionManagerClient::Shutdown();
  }

  void SetPref(const char* name, base::Value value) {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref(
        name, std::make_unique<base::Value>(std::move(value)));
  }

 protected:
  KerberosClient::TestInterface* client_test_interface() {
    return KerberosClient::Get()->GetTestInterface();
  }

  void SetupResultCallback(int operation_count) {
    // If this is the first account addition, sets |result_run_loop_|.
    if (remaining_operation_count_ == 0) {
      EXPECT_TRUE(result_errors_.empty());
      EXPECT_FALSE(result_run_loop_);
      result_run_loop_ = std::make_unique<base::RunLoop>();
    }

    remaining_operation_count_ += operation_count;
  }

  // Gets a callback that adds the passed-in error to |result_errors_|.
  // |operation_count| is the number of times the callback should be called
  // before stopping |result_run_loop_|.
  base::RepeatingCallback<void(kerberos::ErrorType)> GetRepeatingCallback(
      int operation_count) {
    SetupResultCallback(operation_count);
    return base::BindRepeating(&KerberosCredentialsManagerTest::OnResult,
                               weak_ptr_factory_.GetWeakPtr());
  }

  // Gets a callback that adds the passed-in error to |result_errors_|.
  KerberosCredentialsManager::ResultCallback GetResultCallback() {
    SetupResultCallback(1);
    return base::BindOnce(&KerberosCredentialsManagerTest::OnResult,
                          weak_ptr_factory_.GetWeakPtr());
  }

  void OnResult(kerberos::ErrorType error) {
    // Fails if the test tries to execute more operations than expected.
    ASSERT_LT(0, remaining_operation_count_);
    remaining_operation_count_--;
    result_errors_.insert(error);

    // Stops |result_run_loop_| if all additions are finished.
    if (remaining_operation_count_ == 0) {
      result_run_loop_->Quit();
    }
  }

  void WaitAndVerifyResult(std::multiset<kerberos::ErrorType> expected_errors,
                           int expected_notifications_count,
                           int expected_accounts_count) {
    EXPECT_LT(0, remaining_operation_count_);
    ASSERT_TRUE(result_run_loop_);
    result_run_loop_->Run();

    EXPECT_EQ(expected_errors, result_errors_);
    EXPECT_EQ(expected_notifications_count, observer_.notifications_count());
    EXPECT_EQ(expected_accounts_count,
              observer_.accounts_count_at_last_notification());

    EXPECT_EQ(0, remaining_operation_count_);
    result_run_loop_.reset();
    result_errors_.clear();
    observer_.Reset();
  }

  std::multiset<kerberos::ErrorType> GetRepeatedError(kerberos::ErrorType error,
                                                      int repetitions) {
    std::multiset<kerberos::ErrorType> result;
    for (int i = 0; i < repetitions; i++) {
      result.insert(error);
    }

    return result;
  }

  // Calls |mgr_->AddAccountAndAuthenticate()| with |principal_name|,
  // |is_managed| and some default parameters, waits for the result and checks
  // expectations.
  void AddAccountAndAuthenticate(const char* principal_name,
                                 bool is_managed,
                                 kerberos::ErrorType expected_error,
                                 int expected_notifications_count,
                                 int expected_accounts_count) {
    mgr_->AddAccountAndAuthenticate(principal_name, is_managed, kPassword,
                                    kDontRememberPassword, kConfig,
                                    kAllowExisting, GetResultCallback());
    WaitAndVerifyResult({expected_error}, expected_notifications_count,
                        expected_accounts_count);
  }

  void RemoveAccount(const char* principal_name,
                     kerberos::ErrorType expected_error,
                     int expected_notifications_count,
                     int expected_accounts_count) {
    base::RunLoop run_loop;
    mgr_->RemoveAccount(
        principal_name,
        base::BindLambdaForTesting([&](const kerberos::ErrorType error) {
          EXPECT_EQ(expected_error, error);
          run_loop.Quit();
        }));
    run_loop.Run();

    EXPECT_EQ(expected_notifications_count, observer_.notifications_count());
    EXPECT_EQ(expected_accounts_count,
              observer_.accounts_count_at_last_notification());
    observer_.Reset();
  }

  // Calls |mgr_->ListAccounts()|, waits for the result and expects success.
  // Returns the list of accounts.
  Accounts ListAccounts() {
    base::RunLoop run_loop;
    Accounts accounts;
    mgr_->ListAccounts(base::BindLambdaForTesting(
        [&](const kerberos::ListAccountsResponse& response) {
          EXPECT_EQ(kerberos::ERROR_NONE, response.error());
          for (int n = 0; n < response.accounts_size(); ++n) {
            accounts.push_back(std::move(response.accounts(n)));
          }
          run_loop.Quit();
        }));
    run_loop.Run();
    return accounts;
  }

  // Similar to ListAccounts(), but expects exactly 1 account and returns it.
  // Returns a default account if none exists.
  kerberos::Account GetAccount() {
    Accounts accounts = ListAccounts();
    EXPECT_LE(1u, accounts.size());
    if (accounts.size() != 1) {
      return Account();
    }
    return std::move(accounts[0]);
  }

  // Verify KerberosCredentialsManager voted for saving login password for
  // Kerberos service on UserSessionManager with value |save_login_password|.
  void VerifyVotedForSavingLoginPassword(bool save_login_password) {
    // Votes for not saving login password for all the other services. This way,
    // the password should be saved if and only if Kerberos service voted for
    // saving it.
    for (auto s = static_cast<UserSessionManager::PasswordConsumingService>(0);
         s < UserSessionManager::PasswordConsumingService::kCount;
         s = static_cast<UserSessionManager::PasswordConsumingService>(
             static_cast<int>(s) + 1)) {
      if (s != UserSessionManager::PasswordConsumingService::kKerberos) {
        user_session_manager_->VoteForSavingLoginPassword(
            s, kDontSaveLoginPassword);
      }
    }

    // Checks whether the password was saved according to |save_login_password|.
    if (save_login_password == kSaveLoginPassword) {
      EXPECT_EQ(kPassword, FakeSessionManagerClient::Get()->login_password());
    } else {
      EXPECT_TRUE(FakeSessionManagerClient::Get()->login_password().empty());
    }

    // The password should have being deleted from |user_context| at the end.
    const UserContext& user_context = user_session_manager_->user_context();
    EXPECT_TRUE(user_context.GetPasswordKey()->GetSecret().empty());
  }

  // NOTE: InstallAttributes is required to construct BrowserPolicyConnectorAsh.
  // CrosSettings is needed because otherwise TestingProfile automatically
  // creates ScopedCrosSettingsTestHelper, which conflicts with
  // ScopedStubInstallAttributes.
  ScopedTestingCrosSettings scoped_testing_cros_settings_;
  ScopedStubInstallAttributes scoped_stub_install_attributes_;

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  network::TestURLLoaderFactory test_url_loader_factory_;
  user_manager::TypedScopedUserManager<FakeChromeUserManager> user_manager_{
      std::make_unique<FakeChromeUserManager>()};
  std::unique_ptr<UserSessionManager> user_session_manager_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<KerberosCredentialsManager> mgr_;
  FakeKerberosCredentialsManagerObserver observer_;

  int remaining_operation_count_ = 0;
  std::unique_ptr<base::RunLoop> result_run_loop_;
  std::multiset<kerberos::ErrorType> result_errors_;

 private:
  base::WeakPtrFactory<KerberosCredentialsManagerTest> weak_ptr_factory_{this};
};

// The default config sets strong crypto and allows forwardable tickets.
TEST_F(KerberosCredentialsManagerTest, GetDefaultKerberosConfig) {
  const std::string default_config = mgr_->GetDefaultKerberosConfig();

  // Enforce strong crypto.
  EXPECT_TRUE(default_config.contains("default_tgs_enctypes"));
  EXPECT_TRUE(default_config.contains("default_tkt_enctypes"));
  EXPECT_TRUE(default_config.contains("permitted_enctypes"));
  EXPECT_TRUE(default_config.contains("aes256"));
  EXPECT_TRUE(default_config.contains("aes128"));
  EXPECT_FALSE(default_config.contains("des"));
  EXPECT_FALSE(default_config.contains("rc4"));

  // Allow forwardable tickets.
  EXPECT_TRUE(default_config.contains("forwardable = true"));
}

// The ash::prefs::kKerberosEnabled pref toggles IsKerberosEnabled().
TEST_F(KerberosCredentialsManagerTest, IsKerberosEnabled) {
  EXPECT_FALSE(mgr_->IsKerberosEnabled());
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));
  EXPECT_TRUE(mgr_->IsKerberosEnabled());
}

// AddAccountAndAuthenticate() successfully adds an account. The principal name
// is normalized.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateNormalizesPrincipal) {
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kPassword,
                                  kRememberPassword, kConfig,
                                  kDontAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);

  Account account = GetAccount();
  EXPECT_EQ(kNormalizedPrincipal, account.principal_name());
  EXPECT_EQ(kRememberPassword, account.password_was_remembered());
  EXPECT_EQ(kManaged, account.is_managed());
}

// AddAccountAndAuthenticate() fails with ERROR_PARSE_PRINCIPAL_FAILED when a
// bad principal name is passed in.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateFailsForBadPrincipal) {
  const kerberos::ErrorType expected_error =
      kerberos::ERROR_PARSE_PRINCIPAL_FAILED;
  AddAccountAndAuthenticate(kBadPrincipal1, kUnmanaged, expected_error,
                            kNoNotification, kNoAccount);
  AddAccountAndAuthenticate(kBadPrincipal2, kUnmanaged, expected_error,
                            kNoNotification, kNoAccount);
  AddAccountAndAuthenticate(kBadPrincipal3, kUnmanaged, expected_error,
                            kNoNotification, kNoAccount);
  AddAccountAndAuthenticate(kBadPrincipal4, kUnmanaged, expected_error,
                            kNoNotification, kNoAccount);
  AddAccountAndAuthenticate(kBadPrincipal5, kUnmanaged, expected_error,
                            kNoNotification, kNoAccount);
}

// AddAccountAndAuthenticate calls KerberosClient methods in a certain order.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateCallsKerberosClient) {
  // Specifying password includes AcquireKerberosTgt() call.
  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);
  std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig,AcquireKerberosTgt,GetKerberosFiles");

  // Specifying no password excludes AcquireKerberosTgt() call.
  const std::optional<std::string> kNoPassword;
  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kNoPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);
  calls = client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig,GetKerberosFiles");
}

// AddAccountAndAuthenticate rejects existing accounts with
// ERROR_DUPLICATE_PRINCIPAL_NAME if |kDontAllowExisting| is passed in.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateRejectExistingAccount) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kRememberPassword, kConfig,
                                  kDontAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_DUPLICATE_PRINCIPAL_NAME},
                      kOneNotification, kOneAccount);
}

// AddAccountAndAuthenticate succeeds and overwrites existing accounts if
// |kAllowExisting| is passed in.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateAllowExistingAccount) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  EXPECT_FALSE(GetAccount().password_was_remembered());

  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kRememberPassword, kConfig, kAllowExisting,
                                  GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);

  // Check change in password_was_remembered() to validate that the account was
  // overwritten.
  EXPECT_TRUE(GetAccount().password_was_remembered());
}

// AddAccountAndAuthenticate removes accounts added in the same call on failure.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateRemovesAccountOnFailure) {
  // Setting an invalid config causes ERROR_BAD_CONFIG.
  // The previously added account is removed again.
  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kRememberPassword, kInvalidConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_BAD_CONFIG}, kOneNotification,
                      kNoAccount);
  std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig,RemoveAccount");
  EXPECT_EQ(0u, ListAccounts().size());

  // Likewise, setting an invalid password (empty string) causes
  // ERROR_BAD_PASSWORD and the previously added account is removed again.
  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kInvalidPassword,
                                  kRememberPassword, kConfig, kAllowExisting,
                                  GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_BAD_PASSWORD}, kOneNotification,
                      kNoAccount);
  calls = client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig,AcquireKerberosTgt,RemoveAccount");
  EXPECT_EQ(0u, ListAccounts().size());
}

// AddAccountAndAuthenticate does not remove accounts that already existed.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateDoesNotRemoveExistingAccountOnFailure) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);

  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kRememberPassword, kInvalidConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_BAD_CONFIG}, kOneNotification,
                      kOneAccount);
  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig");
  EXPECT_EQ(1u, ListAccounts().size());
}

// AddAccountAndAuthenticate does not remove managed accounts.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateDoesNotRemoveManagedAccountOnFailure) {
  // Setting an invalid config causes ERROR_BAD_CONFIG.
  // The previously added account is removed again.
  client_test_interface()->StartRecordingFunctionCalls();
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kPassword,
                                  kRememberPassword, kInvalidConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_BAD_CONFIG}, kOneNotification,
                      kOneAccount);
  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "AddAccount,SetConfig");
  EXPECT_EQ(1u, ListAccounts().size());
}

// AddAccountAndAuthenticate sets the active account for every UNMANAGED account
// added.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateSetsActiveAccountUnmanaged) {
  // Adding an unmanaged account with no active account sets the active account.
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());

  // Adding another unmanaged account DOES change the active account.
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kUnmanaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kTwoAccounts);
  EXPECT_EQ(kNormalizedOtherPrincipal, mgr_->GetActiveAccount());
}

// AddAccountAndAuthenticate sets the active account only if there was no active
// account if a MANAGED account is added.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateSetsActiveAccountManaged) {
  // Adding a managed account with no active account sets the active account.
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kOneAccount);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());

  // Adding another managed account DOES NOT change the active account.
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  WaitAndVerifyResult({kerberos::ERROR_NONE}, kOneNotification, kTwoAccounts);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// AddAccountAndAuthenticate attempts to add multiple accounts, all of them
// fail, and observers are notified once.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateAddsMultipleAccountsAllFail) {
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kInvalidPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kInvalidPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kYetAnotherPrincipal, kManaged,
                                  kInvalidPassword, kDontRememberPassword,
                                  kConfig, kAllowExisting, GetResultCallback());

  WaitAndVerifyResult(
      GetRepeatedError(kerberos::ERROR_BAD_PASSWORD, kThreeAccounts),
      kOneNotification, kThreeAccounts);
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
}

// AddAccountAndAuthenticate attempts to add multiple accounts, all of them
// succeed, and observers are notified once.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateAddsMultipleAccountsAllSucceed) {
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kYetAnotherPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());

  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kThreeAccounts),
                      kOneNotification, kThreeAccounts);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// AddAccountAndAuthenticate attempts to add multiple accounts, only one
// succeed, and observers are notified once.
TEST_F(KerberosCredentialsManagerTest,
       AddAccountAndAuthenticateAddsMultipleAccountsSingleSuccess) {
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
  mgr_->AddAccountAndAuthenticate(kPrincipal, kManaged, kInvalidPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kPassword,
                                  kDontRememberPassword, kConfig,
                                  kAllowExisting, GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kYetAnotherPrincipal, kManaged,
                                  kInvalidPassword, kDontRememberPassword,
                                  kConfig, kAllowExisting, GetResultCallback());

  WaitAndVerifyResult({kerberos::ERROR_BAD_PASSWORD, kerberos::ERROR_NONE,
                       kerberos::ERROR_BAD_PASSWORD},
                      kOneNotification, kThreeAccounts);
  EXPECT_EQ(kNormalizedOtherPrincipal, mgr_->GetActiveAccount());
}

// RemoveAccount fails with ERROR_PARSE_PRINCIPAL_FAILED when a bad principal
// name is passed in.
TEST_F(KerberosCredentialsManagerTest, RemoveAccountFailsForBadPrincipal) {
  const kerberos::ErrorType expected_error =
      kerberos::ERROR_PARSE_PRINCIPAL_FAILED;
  RemoveAccount(kBadPrincipal1, expected_error, kNoNotification, kNoAccount);
  RemoveAccount(kBadPrincipal2, expected_error, kNoNotification, kNoAccount);
  RemoveAccount(kBadPrincipal3, expected_error, kNoNotification, kNoAccount);
  RemoveAccount(kBadPrincipal4, expected_error, kNoNotification, kNoAccount);
  RemoveAccount(kBadPrincipal5, expected_error, kNoNotification, kNoAccount);
}

// RemoveAccount normalizes |principal_name| before trying to find account.
TEST_F(KerberosCredentialsManagerTest, RemoveAccountNormalizesPrincipalName) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  RemoveAccount(kPrincipal, kerberos::ERROR_NONE, kOneNotification, kNoAccount);
}

// RemoveAccount removes last account, should clear the active principal.
TEST_F(KerberosCredentialsManagerTest,
       RemoveAccountRemoveLastAccountClearsActivePrincipal) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
  client_test_interface()->StartRecordingFunctionCalls();

  RemoveAccount(kPrincipal, kerberos::ERROR_NONE, kOneNotification, kNoAccount);

  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "RemoveAccount");
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
}

// RemoveAccount removes last account, should delete kerberos files.
TEST_F(KerberosCredentialsManagerTest,
       RemoveAccountRemoveLastAccountDeletesKerberosFiles) {
  auto files_handler = std::make_unique<MockKerberosFilesHandler>(
      *TestingBrowserProcess::GetGlobal()->local_state(),
      mgr_->GetGetKerberosFilesCallbackForTesting());
  EXPECT_CALL(*files_handler, DeleteFiles());
  mgr_->SetKerberosFilesHandlerForTesting(std::move(files_handler));
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);

  RemoveAccount(kPrincipal, kerberos::ERROR_NONE, kOneNotification, kNoAccount);
}

// RemoveAccount removes active account while another account is available,
// should set a new active principal.
TEST_F(KerberosCredentialsManagerTest,
       RemoveAccountRemoveActiveAccountAnotherAccountAvailable) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kOtherPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kTwoAccounts);
  EXPECT_EQ(kNormalizedOtherPrincipal, mgr_->GetActiveAccount());
  client_test_interface()->StartRecordingFunctionCalls();

  RemoveAccount(kOtherPrincipal, kerberos::ERROR_NONE, kOneNotification,
                kOneAccount);

  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "RemoveAccount,GetKerberosFiles");
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// RemoveAccount removes non-active account, no change on active account.
TEST_F(KerberosCredentialsManagerTest, RemoveAccountNonActiveAccount) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kOtherPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kTwoAccounts);
  EXPECT_EQ(kNormalizedOtherPrincipal, mgr_->GetActiveAccount());

  client_test_interface()->StartRecordingFunctionCalls();
  RemoveAccount(kPrincipal, kerberos::ERROR_NONE, kOneNotification,
                kOneAccount);

  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "RemoveAccount");
  EXPECT_EQ(kNormalizedOtherPrincipal, mgr_->GetActiveAccount());
}

// RemoveAccount fails to remove unknown account.
TEST_F(KerberosCredentialsManagerTest, RemoveAccountFailsUnknownAccount) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
  client_test_interface()->StartRecordingFunctionCalls();

  RemoveAccount(kOtherPrincipal, kerberos::ERROR_UNKNOWN_PRINCIPAL_NAME,
                kNoNotification, kNoAccount);

  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_EQ(calls, "RemoveAccount");
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// All accounts are wiped when prefs::KerberosEnabled is turned off.
TEST_F(KerberosCredentialsManagerTest, UpdateEnabledFromPrefKerberosDisabled) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));
  EXPECT_TRUE(mgr_->IsKerberosEnabled());

  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kerberos::ERROR_NONE,
                            kOneNotification, kTwoAccounts);

  SetPref(ash::prefs::kKerberosEnabled, base::Value(false));

  EXPECT_FALSE(mgr_->IsKerberosEnabled());
  EXPECT_EQ(0u, ListAccounts().size());
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
}

// Managed accounts are restored when prefs::KerberosEnabled is turned on.
TEST_F(KerberosCredentialsManagerTest, UpdateEnabledFromPrefKerberosEnabled) {
  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback(kTwoAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  EXPECT_FALSE(mgr_->IsKerberosEnabled());
  EXPECT_EQ(0u, ListAccounts().size());
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());

  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  // Two notifications are expected: one from AddAccountRunner and another from
  // RemoveAllManagedAccountsExcept.
  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kTwoAccounts),
                      kTwoNotifications, kTwoAccounts);

  EXPECT_TRUE(mgr_->IsKerberosEnabled());
  EXPECT_EQ(2u, ListAccounts().size());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// No password is deleted when ash::prefs::kKerberosRememberPasswordEnabled is
// turned on.
TEST_F(KerberosCredentialsManagerTest,
       UpdateRememberPasswordEnabledFromPrefEnabled) {
  client_test_interface()->StartRecordingFunctionCalls();

  SetPref(ash::prefs::kKerberosRememberPasswordEnabled, base::Value(true));

  // Checks that ClearAccounts() was not called on KerberosClient.
  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_TRUE(calls.empty());
}

// All unmanaged passwords are deleted when
// ash::prefs::kKerberosRememberPasswordEnabled is turned off.
TEST_F(KerberosCredentialsManagerTest,
       UpdateRememberPasswordEnabledFromPrefDisabled) {
  mgr_->AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kPassword,
                                  kRememberPassword, kConfig, kAllowExisting,
                                  GetResultCallback());
  mgr_->AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kPassword,
                                  kRememberPassword, kConfig, kAllowExisting,
                                  GetResultCallback());

  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kTwoAccounts),
                      kOneNotification, kTwoAccounts);

  SetPref(ash::prefs::kKerberosRememberPasswordEnabled, base::Value(false));

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_FALSE(accounts[0].is_managed());
  EXPECT_FALSE(accounts[0].password_was_remembered());
  EXPECT_TRUE(accounts[1].is_managed());
  EXPECT_TRUE(accounts[1].password_was_remembered());
}

// Setting ash::prefs::kKerberosAddAccountsAllowed to true should not cause any
// account to be deleted.
TEST_F(KerberosCredentialsManagerTest,
       UpdateAddAccountsAllowedFromPrefEnabled) {
  // Starting with KerberosAddAccount disabled.
  SetPref(ash::prefs::kKerberosAddAccountsAllowed, base::Value(false));

  client_test_interface()->StartRecordingFunctionCalls();

  SetPref(ash::prefs::kKerberosAddAccountsAllowed, base::Value(true));

  // Checks that ClearAccounts() was not called on KerberosClient.
  const std::string calls =
      client_test_interface()->StopRecordingAndGetRecordedFunctionCalls();
  EXPECT_TRUE(calls.empty());
}

// All unmanaged accounts are deleted when
// ash::prefs::kKerberosAddAccountsAllowed is turned off.
TEST_F(KerberosCredentialsManagerTest,
       UpdateAddAccountsAllowedFromPrefDisabled) {
  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kerberos::ERROR_NONE,
                            kOneNotification, kTwoAccounts);

  SetPref(ash::prefs::kKerberosAddAccountsAllowed, base::Value(false));

  Accounts accounts = ListAccounts();
  ASSERT_EQ(1u, accounts.size());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[0].principal_name());
}

// UpdateAccountsFromPref votes for not saving the password if Kerberos is
// disabled. Also, no account is added.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefKerberosDisabled) {
  base::DictValue managed_account;
  managed_account.Set(kKeyPrincipal, kPrincipal);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  EXPECT_EQ(0u, ListAccounts().size());
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
}

// UpdateAccountsFromPref votes for not saving the password if no account is
// saved in prefs. Also, existing managed accounts are deleted.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefNoAccounts) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kerberos::ERROR_NONE,
                            kOneNotification, kTwoAccounts);

  base::ListValue managed_accounts;
  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(1u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
}

// UpdateAccountsFromPref ignores accounts with bad principal names.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefBadPrincipal) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;
  base::DictValue managed_account_3;
  base::DictValue managed_account_4;
  base::DictValue managed_account_5;
  base::DictValue managed_account_6;

  managed_account_1.Set(kKeyPrincipal, kBadPrincipal1);
  managed_account_2.Set(kKeyPrincipal, kBadPrincipal2);
  managed_account_3.Set(kKeyPrincipal, kBadPrincipal3);
  managed_account_4.Set(kKeyPrincipal, kBadPrincipal4);
  managed_account_5.Set(kKeyPrincipal, kBadPrincipal5);
  managed_account_6.Set(kKeyPrincipal, kBadManagedPrincipal);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));
  managed_accounts.Append(std::move(managed_account_3));
  managed_accounts.Append(std::move(managed_account_4));
  managed_accounts.Append(std::move(managed_account_5));
  managed_accounts.Append(std::move(managed_account_6));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  EXPECT_EQ(0u, ListAccounts().size());
  EXPECT_TRUE(mgr_->GetActiveAccount().empty());
}

// UpdateAccountsFromPref uses config if given and default config if not.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefConfig) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback(kTwoAccounts));

  base::ListValue config;
  config.Append(base::Value("config line 1"));
  config.Append(base::Value("config line 2"));
  config.Append(base::Value("config line 3"));

  constexpr char expected_config[] =
      "config line 1\nconfig line 2\nconfig line 3\n";

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);
  managed_account_1.Set(kKeyKrb5Conf, std::move(config));

  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
  managed_account_2.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected: one from AddAccountRunner and another from
  // RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kTwoAccounts),
                      kTwoNotifications, kTwoAccounts);

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(expected_config, accounts[0].krb5conf());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[1].principal_name());
  EXPECT_EQ(mgr_->GetDefaultKerberosConfig(), accounts[1].krb5conf());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref votes for saving login password if any of the
// passwords is equal to "${PASSWORD}".
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefPassword) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback(kTwoAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kLoginPasswordPlaceholder);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
  managed_account_2.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected: one from AddAccountRunner and another from
  // RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kTwoAccounts),
                      kTwoNotifications, kTwoAccounts);

  VerifyVotedForSavingLoginPassword(kSaveLoginPassword);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_TRUE(accounts[0].use_login_password());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[1].principal_name());
  EXPECT_FALSE(accounts[1].use_login_password());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref remembers password for accounts with kRememberPassword
// unset or set to true.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefRememberPassword) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback(kThreeAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;
  base::DictValue managed_account_3;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
  managed_account_2.Set(kKeyPassword, kPassword);
  managed_account_2.Set(kKeyRememberPasswordFromPolicy, kRememberPassword);
  managed_account_3.Set(kKeyPrincipal, kYetAnotherPrincipal);
  managed_account_3.Set(kKeyPassword, kPassword);
  managed_account_3.Set(kKeyRememberPasswordFromPolicy, kDontRememberPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));
  managed_accounts.Append(std::move(managed_account_3));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected: one from AddAccountRunner and another from
  // RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kThreeAccounts),
                      kTwoNotifications, kThreeAccounts);

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(3u, accounts.size());
  EXPECT_TRUE(accounts[0].password_was_remembered());
  EXPECT_TRUE(accounts[1].password_was_remembered());
  EXPECT_FALSE(accounts[2].password_was_remembered());
}

// UpdateAccountsFromPref clears out old managed accounts not in
// ash::prefs::kKerberosAccounts anymore.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefClearAccounts) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  AddAccountAndAuthenticate(kPrincipal, kManaged, kerberos::ERROR_NONE,
                            kOneNotification, kOneAccount);
  AddAccountAndAuthenticate(kYetAnotherPrincipal, kManaged,
                            kerberos::ERROR_NONE, kOneNotification,
                            kTwoAccounts);

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback(kTwoAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected: one from AddAccountRunner and another from
  // RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(GetRepeatedError(kerberos::ERROR_NONE, kTwoAccounts),
                      kTwoNotifications, kTwoAccounts);

  VerifyVotedForSavingLoginPassword(kDontSaveLoginPassword);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[1].principal_name());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref retries to add account if addition fails for network
// related errors.
// TODO(crbug.com/40715458): Disabled due to flakiness.
TEST_F(KerberosCredentialsManagerTest, DISABLED_UpdateAccountsFromPrefRetry) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  client_test_interface()->SetSimulatedNumberOfNetworkFailures(kOneFailure *
                                                               kOneAccount);

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback((kOneFailure + 1) * kOneAccount));

  base::DictValue managed_account_1;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected for each attempt: one from AddAccountRunner
  // and another from RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult({kerberos::ERROR_NETWORK_PROBLEM, kerberos::ERROR_NONE},
                      (kOneFailure + 1) * kTwoNotifications, kOneAccount);

  // Fast forwarding the task environment to force all pending tasks to be
  // executed. Makes sure no retry is scheduled after a successful attempt.
  task_environment_.FastForwardBy(kLongTimeDelay);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(1u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref retries multiple times to add account if addition
// fails multiple times for network related errors.
// Disable for being flaky. crbug.com/1116500
TEST_F(KerberosCredentialsManagerTest,
       DISABLED_UpdateAccountsFromPrefMultipleRetries) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  client_test_interface()->SetSimulatedNumberOfNetworkFailures(kThreeFailures *
                                                               kOneAccount);

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback((kThreeFailures + 1) * kOneAccount));

  base::DictValue managed_account_1;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected for each attempt: one from AddAccountRunner
  // and another from RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(
      {kerberos::ERROR_NETWORK_PROBLEM, kerberos::ERROR_NETWORK_PROBLEM,
       kerberos::ERROR_NETWORK_PROBLEM, kerberos::ERROR_NONE},
      (kThreeFailures + 1) * kTwoNotifications, kOneAccount);

  // Fast forwarding the task environment to force all pending tasks to be
  // executed. This will make sure no retry is scheduled after a successful
  // attempt.
  task_environment_.FastForwardBy(kLongTimeDelay);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(1u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref retries to add multiple accounts if addition fails for
// network related errors.
// Disable for being flaky. crbug.com/1116500
TEST_F(KerberosCredentialsManagerTest,
       DISABLED_UpdateAccountsFromPrefRetryMultipleAccounts) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  client_test_interface()->SetSimulatedNumberOfNetworkFailures(kOneFailure *
                                                               kTwoAccounts);

  mgr_->SetAddManagedAccountCallbackForTesting(
      GetRepeatingCallback((kOneFailure + 1) * kTwoAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
  managed_account_2.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected for each attempt: one from AddAccountRunner
  // and another from RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(
      {kerberos::ERROR_NETWORK_PROBLEM, kerberos::ERROR_NETWORK_PROBLEM,
       kerberos::ERROR_NONE, kerberos::ERROR_NONE},
      (kOneFailure + 1) * kTwoNotifications, kTwoAccounts);

  // Fast forwarding the task environment to force all pending tasks to be
  // executed. This will make sure no retry is scheduled after a successful
  // attempt.
  task_environment_.FastForwardBy(kLongTimeDelay);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[1].principal_name());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// UpdateAccountsFromPref stops retrying after a certain number of network
// related errors.
// Disable for being flaky. crbug.com/1116500
TEST_F(KerberosCredentialsManagerTest,
       DISABLED_UpdateAccountsFromPrefStopsRetrying) {
  // Starting with Kerberos enabled.
  SetPref(ash::prefs::kKerberosEnabled, base::Value(true));

  client_test_interface()->SetSimulatedNumberOfNetworkFailures(kLotsOfFailures);

  mgr_->SetAddManagedAccountCallbackForTesting(GetRepeatingCallback(
      KerberosCredentialsManager::kMaxFailureCountForManagedAccounts *
      kTwoAccounts));

  base::DictValue managed_account_1;
  base::DictValue managed_account_2;

  managed_account_1.Set(kKeyPrincipal, kPrincipal);
  managed_account_1.Set(kKeyPassword, kPassword);
  managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
  managed_account_2.Set(kKeyPassword, kPassword);

  base::ListValue managed_accounts;
  managed_accounts.Append(std::move(managed_account_1));
  managed_accounts.Append(std::move(managed_account_2));

  SetPref(ash::prefs::kKerberosAccounts,
          base::Value(std::move(managed_accounts)));

  // Two notifications are expected for each attempt: one from AddAccountRunner
  // and another from RemoveAllManagedAccountsExcept().
  WaitAndVerifyResult(
      GetRepeatedError(
          kerberos::ERROR_NETWORK_PROBLEM,
          KerberosCredentialsManager::kMaxFailureCountForManagedAccounts *
              kTwoAccounts),
      KerberosCredentialsManager::kMaxFailureCountForManagedAccounts *
          kTwoNotifications,
      kTwoAccounts);

  // Fast forwarding the task environment to force all pending tasks to be
  // executed. This will make sure no retry is scheduled after a certain number
  // of network related errors.
  task_environment_.FastForwardBy(kLongTimeDelay);

  Accounts accounts = ListAccounts();
  ASSERT_EQ(2u, accounts.size());
  EXPECT_EQ(kNormalizedPrincipal, accounts[0].principal_name());
  EXPECT_EQ(kNormalizedOtherPrincipal, accounts[1].principal_name());
  EXPECT_EQ(kNormalizedPrincipal, mgr_->GetActiveAccount());
}

// TODO(https://crbug.com/259178156): Add more tests
// - ClearAccounts
//     + Normalization like in AddAccountAndAuthenticate
//     + Calls the ClearAccounts KerberosClient method
//     + If CLEAR_ALL, CLEAR_ONLY_*_ACCOUNTS: Reassigns active principal if it
//       was deleted.
//     + On success, calls OnAccountsChanged on observers
// - ListAccounts
//     + Normalization like in AddAccountAndAuthenticate
//     + Calls the ListAccounts KerberosClient method
//     + Reassigns an active principal if it was empty
// - SetConfig
//     + Normalization like in AddAccountAndAuthenticate
//     + Calls the SetConfig KerberosClient method
//     + On success, calls OnAccountsChanged on observers
// - ValidateConfig
//     + Normalization like in AddAccountAndAuthenticate
//     + Calls the ValidateConfig KerberosClient method
// - SetActiveAccount
//     + Calls OnAccountsChanged on observers
// - GetKerberosFiles
//     + Earlies out if the active principal is empty
//     + Calls the GetKerberosFiles KerberosClient method
//     + Does nothing if active principal changed during the async call
//     + On success, calls kerberos_files_handler_.SetFiles if there's a
//     krb5cc
//     + On success, calls kerberos_files_handler_.DeleteFiles otherwise
// - OnKerberosFilesChanged
//     + Gets called when KerberosClient fires the KerberosFilesChanged signal
//     + Calls GetKerberosFiles if the active principal matches.
// - OnKerberosTicketExpiring
//     + Gets called when KerberosClient fires the KerberosTicketExpiring
//     signal
//     + Calls kerberos_ticket_expiry_notification::Show() if the active
//       principal matches.
// - Notification
//     + If the notification is clicked, shows the KerberosAccounts settings
//       with ?kerberos_reauth=<principal name>
// - If policy service finishes initialization after constructor of
//   KerberosCredentialsManager, UpdateAccountsFromPref is called.
//
// See also
//   https://analysis.chromium.org/p/chromium/coverage/dir?host=chromium.googlesource.com&project=chromium/src&ref=refs/heads/main&path=//chrome/browser/ash/kerberos/&platform=linux-chromeos
// for code coverage (try to get as high as possible!).

}  // namespace ash
