blob: b88d09472270c2b8e3fca6c51ae3468a6598ee81 [file] [log] [blame]
// 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 "base/containers/contains.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/notifications/notification_display_service_tester.h"
#include "chrome/common/pref_names.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 "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 "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());
user_manager_->AddUser(AccountId::FromUserEmail(kProfileEmail));
// Setting the login password for the KerberosAccounts policy tests.
UserContext* user_context =
UserSessionManager::GetInstance()->mutable_user_context_for_testing();
user_context->SetPasswordKey(Key(kPassword));
UserSessionManager::GetInstance()->set_start_session_type_for_testing(
UserSessionManager::StartSessionType::kPrimary);
TestingProfile::Builder profile_builder;
profile_builder.SetProfileName(kProfileEmail);
profile_ = profile_builder.Build();
display_service_ =
std::make_unique<NotificationDisplayServiceTester>(profile_.get());
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();
display_service_.reset();
profile_.reset();
UserSessionManager::GetInstance()->Shutdown();
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) {
UserSessionManager::GetInstance()->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 =
UserSessionManager::GetInstance()->user_context();
EXPECT_TRUE(user_context.GetPasswordKey()->GetSecret().empty());
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
user_manager::TypedScopedUserManager<FakeChromeUserManager> user_manager_{
std::make_unique<FakeChromeUserManager>()};
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_;
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(base::Contains(default_config, "default_tgs_enctypes"));
EXPECT_TRUE(base::Contains(default_config, "default_tkt_enctypes"));
EXPECT_TRUE(base::Contains(default_config, "permitted_enctypes"));
EXPECT_TRUE(base::Contains(default_config, "aes256"));
EXPECT_TRUE(base::Contains(default_config, "aes128"));
EXPECT_FALSE(base::Contains(default_config, "des"));
EXPECT_FALSE(base::Contains(default_config, "rc4"));
// Allow forwardable tickets.
EXPECT_TRUE(base::Contains(default_config, "forwardable = true"));
}
// The prefs::kKerberosEnabled pref toggles IsKerberosEnabled().
TEST_F(KerberosCredentialsManagerTest, IsKerberosEnabled) {
EXPECT_FALSE(mgr_->IsKerberosEnabled());
SetPref(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(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(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::Value::Dict managed_account_1;
base::Value::Dict managed_account_2;
managed_account_1.Set(kKeyPrincipal, kPrincipal);
managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
base::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(prefs::kKerberosAccounts, base::Value(std::move(managed_accounts)));
EXPECT_FALSE(mgr_->IsKerberosEnabled());
EXPECT_EQ(0u, ListAccounts().size());
EXPECT_TRUE(mgr_->GetActiveAccount().empty());
SetPref(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 prefs::kKerberosRememberPasswordEnabled is turned
// on.
TEST_F(KerberosCredentialsManagerTest,
UpdateRememberPasswordEnabledFromPrefEnabled) {
client_test_interface()->StartRecordingFunctionCalls();
SetPref(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
// 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(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 prefs::kKerberosAddAccountsAllowed to true should not cause any
// account to be deleted.
TEST_F(KerberosCredentialsManagerTest,
UpdateAddAccountsAllowedFromPrefEnabled) {
// Starting with KerberosAddAccount disabled.
SetPref(prefs::kKerberosAddAccountsAllowed, base::Value(false));
client_test_interface()->StartRecordingFunctionCalls();
SetPref(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 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(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::Value::Dict managed_account;
managed_account.Set(kKeyPrincipal, kPrincipal);
base::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
AddAccountAndAuthenticate(kPrincipal, kUnmanaged, kerberos::ERROR_NONE,
kOneNotification, kOneAccount);
AddAccountAndAuthenticate(kOtherPrincipal, kManaged, kerberos::ERROR_NONE,
kOneNotification, kTwoAccounts);
base::Value::List managed_accounts;
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
base::Value::Dict managed_account_1;
base::Value::Dict managed_account_2;
base::Value::Dict managed_account_3;
base::Value::Dict managed_account_4;
base::Value::Dict managed_account_5;
base::Value::Dict 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::Value::List 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(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(prefs::kKerberosEnabled, base::Value(true));
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback(kTwoAccounts));
base::Value::List 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::Value::Dict managed_account_1;
base::Value::Dict 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::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback(kTwoAccounts));
base::Value::Dict managed_account_1;
base::Value::Dict 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::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback(kThreeAccounts));
base::Value::Dict managed_account_1;
base::Value::Dict managed_account_2;
base::Value::Dict 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::Value::List 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(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
// prefs::kKerberosAccounts anymore.
TEST_F(KerberosCredentialsManagerTest, UpdateAccountsFromPrefClearAccounts) {
// Starting with Kerberos enabled.
SetPref(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::Value::Dict managed_account_1;
base::Value::Dict managed_account_2;
managed_account_1.Set(kKeyPrincipal, kPrincipal);
managed_account_2.Set(kKeyPrincipal, kOtherPrincipal);
base::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
client_test_interface()->SetSimulatedNumberOfNetworkFailures(kOneFailure *
kOneAccount);
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback((kOneFailure + 1) * kOneAccount));
base::Value::Dict managed_account_1;
managed_account_1.Set(kKeyPrincipal, kPrincipal);
managed_account_1.Set(kKeyPassword, kPassword);
base::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
client_test_interface()->SetSimulatedNumberOfNetworkFailures(kThreeFailures *
kOneAccount);
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback((kThreeFailures + 1) * kOneAccount));
base::Value::Dict managed_account_1;
managed_account_1.Set(kKeyPrincipal, kPrincipal);
managed_account_1.Set(kKeyPassword, kPassword);
base::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
client_test_interface()->SetSimulatedNumberOfNetworkFailures(kOneFailure *
kTwoAccounts);
mgr_->SetAddManagedAccountCallbackForTesting(
GetRepeatingCallback((kOneFailure + 1) * kTwoAccounts));
base::Value::Dict managed_account_1;
base::Value::Dict 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::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(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(prefs::kKerberosEnabled, base::Value(true));
client_test_interface()->SetSimulatedNumberOfNetworkFailures(kLotsOfFailures);
mgr_->SetAddManagedAccountCallbackForTesting(GetRepeatingCallback(
KerberosCredentialsManager::kMaxFailureCountForManagedAccounts *
kTwoAccounts));
base::Value::Dict managed_account_1;
base::Value::Dict 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::Value::List managed_accounts;
managed_accounts.Append(std::move(managed_account_1));
managed_accounts.Append(std::move(managed_account_2));
SetPref(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/952251): 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