blob: 0df50cef96962478088f8dc2025b302d55f0c94d [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "kerberos/account_manager.h"
#include <map>
#include <memory>
#include <utility>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "kerberos/fake_krb5_interface.h"
namespace {
constexpr char kUser[] = "user@REALM.COM";
constexpr char kUser2[] = "user2@REALM2.COM";
constexpr char kPassword[] = "i<3k3R8e5Oz";
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_realm = REALM.COM)";
} // namespace
namespace kerberos {
class AccountManagerTest : public ::testing::Test {
public:
AccountManagerTest()
: kerberos_files_changed_(
base::BindRepeating(&AccountManagerTest::OnKerberosFilesChanged,
base::Unretained(this))) {}
~AccountManagerTest() override = default;
void SetUp() override {
::testing::Test::SetUp();
// Create temp directory for files written during tests.
CHECK(storage_dir_.CreateUniqueTempDir());
accounts_path_ = storage_dir_.GetPath().Append("accounts");
const std::string hash = AccountManager::HashPrincipalForTesting(kUser);
krb5cc_path_ = storage_dir_.GetPath().Append(hash + "_krb5cc");
krb5conf_path_ = storage_dir_.GetPath().Append(hash + "_krb5.conf");
// Create the manager with a fake krb5 interface.
auto krb5 = std::make_unique<FakeKrb5Interface>();
krb5_ = krb5.get();
manager_ = std::make_unique<AccountManager>(
storage_dir_.GetPath(), kerberos_files_changed_, std::move(krb5));
}
void TearDown() override {
// Make sure the file stored on disk contains the same accounts as the
// manager instance. This catches cases where AccountManager forgets to save
// accounts on some change.
if (base::PathExists(accounts_path_)) {
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
AccountManager other_manager(storage_dir_.GetPath(),
kerberos_files_changed_,
std::make_unique<FakeKrb5Interface>());
other_manager.LoadAccounts();
std::vector<Account> other_accounts;
EXPECT_EQ(ERROR_NONE, other_manager.ListAccounts(&other_accounts));
ASSERT_NO_FATAL_FAILURE(ExpectAccountsEqual(accounts, other_accounts));
}
::testing::Test::TearDown();
}
protected:
void OnKerberosFilesChanged(const std::string& principal_name) {
kerberos_files_changed_count_[principal_name]++;
}
void ExpectAccountsEqual(const std::vector<Account>& account_list_1,
const std::vector<Account>& account_list_2) {
ASSERT_EQ(account_list_1.size(), account_list_2.size());
for (size_t n = 0; n < account_list_1.size(); ++n) {
const Account& account1 = account_list_1[n];
const Account& account2 = account_list_2[n];
EXPECT_EQ(account1.principal_name(), account2.principal_name());
EXPECT_EQ(account1.is_managed(), account2.is_managed());
// TODO(https://crbug.com/952239): Check additional properties.
}
}
std::unique_ptr<AccountManager> manager_;
// Kerberos interface used by |manager_|. Not owned.
FakeKrb5Interface* krb5_;
// Paths of files stored by |manager_|.
base::ScopedTempDir storage_dir_;
base::FilePath accounts_path_;
base::FilePath krb5conf_path_;
base::FilePath krb5cc_path_;
AccountManager::KerberosFilesChangedCallback kerberos_files_changed_;
std::map<std::string, int> kerberos_files_changed_count_;
private:
DISALLOW_COPY_AND_ASSIGN(AccountManagerTest);
};
// Adding an account succeeds and serializes the file on disk.
TEST_F(AccountManagerTest, AddAccountSuccess) {
EXPECT_FALSE(base::PathExists(accounts_path_));
EXPECT_EQ(ERROR_NONE, manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_TRUE(base::PathExists(accounts_path_));
}
// AddAccount() fails if the same account is added twice.
TEST_F(AccountManagerTest, AddDuplicateAccountFail) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_TRUE(base::DeleteFile(accounts_path_, false /* recursive */));
EXPECT_EQ(ERROR_DUPLICATE_PRINCIPAL_NAME,
manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_FALSE(base::PathExists(accounts_path_));
}
// Adding a managed account overwrites an existing unmanaged account.
TEST_F(AccountManagerTest, ManagedOverridesUnmanaged) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
// Overwriting with a managed account should wipe existing files and make the
// account managed.
EXPECT_EQ(ERROR_DUPLICATE_PRINCIPAL_NAME,
manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_FALSE(base::PathExists(krb5cc_path_));
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
ASSERT_EQ(1u, accounts.size());
EXPECT_TRUE(accounts[0].is_managed());
}
// Adding an unmanaged account does not overwrite an existing managed account.
TEST_F(AccountManagerTest, UnmanagedDoesNotOverrideManaged) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_DUPLICATE_PRINCIPAL_NAME,
manager_->AddAccount(kUser, false /* is_managed */));
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
ASSERT_EQ(1u, accounts.size());
EXPECT_TRUE(accounts[0].is_managed());
}
// RemoveAccount() succeeds if the account exists and serializes the file on
// disk.
TEST_F(AccountManagerTest, RemoveAccountSuccess) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_TRUE(base::DeleteFile(accounts_path_, false /* recursive */));
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
EXPECT_TRUE(base::PathExists(accounts_path_));
}
// RemoveAccount() fails if the account does not exist.
TEST_F(AccountManagerTest, RemoveUnknownAccountFail) {
EXPECT_EQ(ERROR_UNKNOWN_PRINCIPAL_NAME, manager_->RemoveAccount(kUser));
EXPECT_FALSE(base::PathExists(accounts_path_));
}
// RemoveAccount() does not trigger KerberosFilesChanged if the credential cache
// does not exists.
TEST_F(AccountManagerTest, RemoveAccountTriggersKFCIfCCExists) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
EXPECT_EQ(0, kerberos_files_changed_count_[kUser]);
}
// RemoveAccount() triggers KerberosFilesChanged if the credential cache exists.
TEST_F(AccountManagerTest, RemoveAccountDoesNotTriggerKFCIfCCDoesNotExist) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(1, kerberos_files_changed_count_[kUser]);
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
EXPECT_EQ(2, kerberos_files_changed_count_[kUser]);
}
// Repeatedly calling AddAccount() and RemoveAccount() succeeds.
TEST_F(AccountManagerTest, RepeatedAddRemoveSuccess) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
ignore_result(manager_->RemoveAccount(kUser));
EXPECT_EQ(ERROR_NONE, manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
}
// ClearAccount() clears all accounts.
TEST_F(AccountManagerTest, ClearAccountSuccess) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
ignore_result(manager_->AddAccount(kUser2, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->ClearAccounts());
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
EXPECT_EQ(0u, accounts.size());
}
// ClearAccount() wipes Kerberos configuration and credential cache.
TEST_F(AccountManagerTest, ClearAccountRemovesKerberosFiles) {
ignore_result(manager_->AddAccount(kUser, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->SetConfig(kUser, kKrb5Conf));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5conf_path_));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
EXPECT_EQ(ERROR_NONE, manager_->ClearAccounts());
EXPECT_FALSE(base::PathExists(krb5conf_path_));
EXPECT_FALSE(base::PathExists(krb5cc_path_));
}
// ClearAccount() triggers KerberosFilesChanged if the credential cache exists.
TEST_F(AccountManagerTest, ClearAccountTriggersKFCIfCCExists) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(1, kerberos_files_changed_count_[kUser]);
EXPECT_EQ(ERROR_NONE, manager_->ClearAccounts());
EXPECT_EQ(2, kerberos_files_changed_count_[kUser]);
}
// ClearAccount() does not trigger KerberosFilesChanged if the credential cache
// does not exist.
TEST_F(AccountManagerTest, ClearAccountDoesNotTriggerKFCIfDoesNotCCExist) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->ClearAccounts());
EXPECT_EQ(0, kerberos_files_changed_count_[kUser]);
}
// SetConfig() succeeds and writes the config to |krb5conf_path_|.
TEST_F(AccountManagerTest, SetConfigSuccess) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->SetConfig(kUser, kKrb5Conf));
std::string krb5_conf;
EXPECT_TRUE(base::ReadFileToString(krb5conf_path_, &krb5_conf));
EXPECT_EQ(krb5_conf, kKrb5Conf);
}
// SetConfig() triggers KerberosFilesChanged if the credential cache exists.
TEST_F(AccountManagerTest, SetConfigTriggersKFCIfCCExists) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(1, kerberos_files_changed_count_[kUser]);
EXPECT_EQ(ERROR_NONE, manager_->SetConfig(kUser, kKrb5Conf));
EXPECT_EQ(2, kerberos_files_changed_count_[kUser]);
}
// SetConfig() does not trigger KerberosFilesChanged if the credential cache
// does not exist.
TEST_F(AccountManagerTest, SetConfigDoesNotTriggerKFCIfDoesNotCCExist) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->SetConfig(kUser, kKrb5Conf));
EXPECT_EQ(0, kerberos_files_changed_count_[kUser]);
}
// RemoveAccount() removes the config file.
TEST_F(AccountManagerTest, RemoveAccountRemovesConfig) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->SetConfig(kUser, kKrb5Conf));
EXPECT_TRUE(base::PathExists(krb5conf_path_));
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
EXPECT_FALSE(base::PathExists(krb5conf_path_));
}
// AcquireTgt() succeeds and writes a credential cache file.
TEST_F(AccountManagerTest, AcquireTgtSuccess) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
}
// AcquireTgt() triggers KerberosFilesChanged on success.
TEST_F(AccountManagerTest, AcquireTgtTriggersKFCOnSuccess) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
EXPECT_EQ(0, kerberos_files_changed_count_[kUser]);
EXPECT_EQ(ERROR_NONE, manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(1, kerberos_files_changed_count_[kUser]);
}
// AcquireTgt() does not trigger KerberosFilesChanged on failure.
TEST_F(AccountManagerTest, AcquireTgtDoesNotTriggerKFCOnFailure) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
krb5_->set_acquire_tgt_error(ERROR_UNKNOWN);
EXPECT_EQ(ERROR_UNKNOWN, manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(0, kerberos_files_changed_count_[kUser]);
}
// RemoveAccount() removes the credential cache file.
TEST_F(AccountManagerTest, RemoveAccountRemovesCC) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
EXPECT_EQ(ERROR_NONE, manager_->RemoveAccount(kUser));
EXPECT_FALSE(base::PathExists(krb5cc_path_));
}
// ListAccounts() succeeds and contains the expected data.
TEST_F(AccountManagerTest, ListAccountsSuccess) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->SetConfig(kUser, kKrb5Conf));
ignore_result(manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
// Set a fake tgt status.
constexpr int kRenewalSeconds = 10;
constexpr int kValiditySeconds = 90;
Krb5Interface::TgtStatus status;
status.renewal_seconds = kRenewalSeconds;
status.validity_seconds = kValiditySeconds;
krb5_->set_tgt_status(std::move(status));
// Verify that ListAccounts returns the expected account.
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
ASSERT_EQ(1u, accounts.size());
EXPECT_EQ(kUser, accounts[0].principal_name());
EXPECT_EQ(kKrb5Conf, accounts[0].krb5conf());
EXPECT_EQ(kRenewalSeconds, accounts[0].tgt_renewal_seconds());
EXPECT_EQ(kValiditySeconds, accounts[0].tgt_validity_seconds());
EXPECT_TRUE(accounts[0].is_managed());
}
// ListAccounts() ignores failures in GetTgtStatus() and loading the config.
TEST_F(AccountManagerTest, ListAccountsIgnoresFailures) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->SetConfig(kUser, kKrb5Conf));
ignore_result(manager_->AcquireTgt(kUser, kPassword));
EXPECT_TRUE(base::PathExists(krb5cc_path_));
// Make reading the config fail.
EXPECT_TRUE(base::SetPosixFilePermissions(krb5conf_path_, 0));
// Make GetTgtStatus() fail.
krb5_->set_get_tgt_status_error(ERROR_UNKNOWN);
// ListAccounts() should still work, despite the errors.
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, manager_->ListAccounts(&accounts));
ASSERT_EQ(1u, accounts.size());
EXPECT_EQ(kUser, accounts[0].principal_name());
// The config should not be set since we made reading the file fail.
EXPECT_FALSE(accounts[0].has_krb5conf());
// tgt_*_seconds should not be set since we made GetTgtStatus() fail.
EXPECT_FALSE(accounts[0].has_tgt_renewal_seconds());
EXPECT_FALSE(accounts[0].has_tgt_validity_seconds());
}
// GetKerberosFiles returns empty KerberosFiles if there is no credential cache,
// even if there is a config.
TEST_F(AccountManagerTest, GetKerberosFilesSucceesWithoutCC) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->SetConfig(kUser, kKrb5Conf));
KerberosFiles files;
EXPECT_EQ(ERROR_NONE, manager_->GetKerberosFiles(kUser, &files));
EXPECT_FALSE(files.has_krb5cc());
EXPECT_FALSE(files.has_krb5conf());
}
// GetKerberosFiles returns the expected KerberosFiles if there is a credential
// cache.
TEST_F(AccountManagerTest, GetKerberosFilesSucceesWithCC) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->SetConfig(kUser, kKrb5Conf));
ignore_result(manager_->AcquireTgt(kUser, kPassword));
KerberosFiles files;
EXPECT_EQ(ERROR_NONE, manager_->GetKerberosFiles(kUser, &files));
EXPECT_FALSE(files.krb5cc().empty());
EXPECT_EQ(kKrb5Conf, files.krb5conf());
}
// Most methods return ERROR_UNKNOWN_PRINCIPAL if called with such a principal.
TEST_F(AccountManagerTest, MethodsReturnUnknownPrincipal) {
KerberosFiles files;
EXPECT_EQ(ERROR_UNKNOWN_PRINCIPAL_NAME, manager_->RemoveAccount(kUser));
EXPECT_EQ(ERROR_UNKNOWN_PRINCIPAL_NAME,
manager_->SetConfig(kUser, kKrb5Conf));
EXPECT_EQ(ERROR_UNKNOWN_PRINCIPAL_NAME,
manager_->AcquireTgt(kUser, kPassword));
EXPECT_EQ(ERROR_UNKNOWN_PRINCIPAL_NAME,
manager_->GetKerberosFiles(kUser, &files));
}
// Accounts can be saved to disk and loaded from disk.
TEST_F(AccountManagerTest, SerializationSuccess) {
ignore_result(manager_->AddAccount(kUser, true /* is_managed */));
ignore_result(manager_->AddAccount(kUser2, false /* is_managed */));
EXPECT_EQ(ERROR_NONE, manager_->SaveAccounts());
AccountManager other_manager(storage_dir_.GetPath(), kerberos_files_changed_,
std::make_unique<FakeKrb5Interface>());
other_manager.LoadAccounts();
std::vector<Account> accounts;
EXPECT_EQ(ERROR_NONE, other_manager.ListAccounts(&accounts));
ASSERT_EQ(2u, accounts.size());
EXPECT_EQ(kUser, accounts[0].principal_name());
EXPECT_EQ(kUser2, accounts[1].principal_name());
EXPECT_TRUE(accounts[0].is_managed());
EXPECT_FALSE(accounts[1].is_managed());
// TODO(https://crbug.com/952239): Check additional Account properties.
}
} // namespace kerberos