0

Reland "[High5] ActiveSessionAuthController unittests"

This is a reland of commit 3d5950913d

Original change's description:
> [High5] ActiveSessionAuthController unittests
>
> We add several unittests that test the behavior of
> `ActiveSessionAuthController`. We assert that it behaves correctly in
> the case of correct and wrong password/pin inputs, and in the case of
> canceling the dialog.
>
> Bug: b:352238958, b:348326316
> Change-Id: I141d45f932ad9884253480e578c413ec61d948ab
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5735972
> Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
> Reviewed-by: Maksim Ivanov <emaxx@chromium.org>
> Reviewed-by: Hardik Goyal <hardikgoyal@chromium.org>
> Commit-Queue: Elie Maamari <emaamari@google.com>
> Cr-Commit-Position: refs/heads/main@{#1333500}

Bug: b:352238958, b:348326316
Change-Id: I1aa313d56559aac0626cdf38417873feb9607be6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5739039
Reviewed-by: Hardik Goyal <hardikgoyal@chromium.org>
Reviewed-by: Maksim Ivanov <emaxx@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Hardik Goyal <hardikgoyal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1334426}
This commit is contained in:
Elie Maamari
2024-07-29 20:35:17 +00:00
committed by Chromium LUCI CQ
parent b54148228b
commit dc5fcb16b1
6 changed files with 505 additions and 4 deletions

@ -3651,6 +3651,7 @@ test("ash_unittests") {
"assistant/ui/main_stage/ui_element_container_view_unittest.cc",
"assistant/util/deep_link_util_unittest.cc",
"assistant/util/resource_util_unittest.cc",
"auth/test/active_session_auth_controller_unittest.cc",
"auth/views/active_session_auth_view_unittest.cc",
"auth/views/auth_container_view_unittest.cc",
"auth/views/auth_input_row_view_unittest.cc",

@ -83,6 +83,31 @@ std::unique_ptr<views::Widget> CreateAuthDialogWidget(
} // namespace
ActiveSessionAuthControllerImpl::TestApi::TestApi(
ActiveSessionAuthControllerImpl* controller)
: controller_(controller) {}
ActiveSessionAuthControllerImpl::TestApi::~TestApi() = default;
AuthFactorSet ActiveSessionAuthControllerImpl::TestApi::GetAvailableFactors()
const {
return controller_->available_factors_;
}
void ActiveSessionAuthControllerImpl::TestApi::SubmitPassword(
const std::string& password) {
controller_->OnPasswordSubmit(base::UTF8ToUTF16(password));
}
void ActiveSessionAuthControllerImpl::TestApi::SubmitPin(
const std::string& pin) {
controller_->OnPinSubmit(base::UTF8ToUTF16(pin));
}
void ActiveSessionAuthControllerImpl::TestApi::Close() {
controller_->Close();
}
ActiveSessionAuthControllerImpl::ActiveSessionAuthControllerImpl() = default;
ActiveSessionAuthControllerImpl::~ActiveSessionAuthControllerImpl() = default;
@ -153,17 +178,16 @@ void ActiveSessionAuthControllerImpl::OnAuthFactorsListed(
}
const auto& config = user_context->GetAuthFactorsConfiguration();
AuthFactorSet available_factors;
if (config.FindFactorByType(cryptohome::AuthFactorType::kPassword)) {
available_factors.Put(AuthInputType::kPassword);
available_factors_.Put(AuthInputType::kPassword);
}
if (config.FindFactorByType(cryptohome::AuthFactorType::kPin)) {
available_factors.Put(AuthInputType::kPin);
available_factors_.Put(AuthInputType::kPin);
}
auto contents_view = std::make_unique<ActiveSessionAuthView>(
account_id_, title_, description_, available_factors);
account_id_, title_, description_, available_factors_);
contents_view_ = contents_view.get();
widget_ = CreateAuthDialogWidget(std::move(contents_view));
@ -200,6 +224,8 @@ void ActiveSessionAuthControllerImpl::Close() {
if (user_context_) {
user_context_.reset();
}
available_factors_.Clear();
}
void ActiveSessionAuthControllerImpl::OnViewPreferredSizeChanged(

@ -37,6 +37,31 @@ class ASH_EXPORT ActiveSessionAuthControllerImpl
public views::ViewObserver,
public InSessionAuthTokenProvider {
public:
class TestApi {
public:
explicit TestApi(ActiveSessionAuthControllerImpl* controller);
~TestApi();
TestApi(const TestApi&) = delete;
TestApi& operator=(const TestApi&) = delete;
// Returns the known-to-be-available factors that `ActiveSessionAuthView`
// was rendered with.
AuthFactorSet GetAvailableFactors() const;
// Simulates submitting the `password` to cryptohome as if the user
// manually entered it.
void SubmitPassword(const std::string& password);
// Simulates submitting the `pin` to cryptohome as if the user
// manually entered it.
void SubmitPin(const std::string& pin);
void Close();
private:
const raw_ptr<ActiveSessionAuthControllerImpl> controller_;
};
ActiveSessionAuthControllerImpl();
ActiveSessionAuthControllerImpl(const ActiveSessionAuthControllerImpl&) =
delete;
@ -99,6 +124,8 @@ class ASH_EXPORT ActiveSessionAuthControllerImpl
std::unique_ptr<UserContext> user_context_;
AuthFactorSet available_factors_;
base::WeakPtrFactory<ActiveSessionAuthControllerImpl> weak_ptr_factory_{this};
};

@ -0,0 +1,417 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/auth/active_session_auth_controller_impl.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/osauth/impl/auth_surface_registry.h"
#include "chromeos/ash/components/osauth/public/auth_parts.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
namespace ash {
namespace {
constexpr char kUserEmail[] = "expected_email@example.com";
constexpr char kExpectedPassword[] = "expected_password";
constexpr char kExpectedPin[] = "123456";
constexpr char kExpectedSalt[] = "test salt";
} // namespace
class ActiveSessionAuthControllerTest : public NoSessionAshTestBase {
public:
using OnAuthComplete =
base::test::TestFuture<bool, const ash::AuthProofToken&, base::TimeDelta>;
ActiveSessionAuthControllerTest() {
InitializeUserManager();
AddUserToUserManager();
SystemSaltGetter::Initialize();
CryptohomeMiscClient::InitializeFake();
UserDataAuthClient::InitializeFake();
auth_parts_ = AuthParts::Create(&local_state_);
}
void SetUp() override {
AshTestBase::SetUp();
GetSessionControllerClient()->DisableAutomaticallyProvideSigninPref();
GetSessionControllerClient()->Reset();
GetSessionControllerClient()->AddUserSession(
kUserEmail, user_manager::UserType::kRegular);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
}
void TearDown() override {
Shell::Get()->session_controller()->ClearUserSessionsForTest();
auth_parts_.reset();
user_manager_->Destroy();
SystemSaltGetter::Shutdown();
CryptohomeMiscClient::Shutdown();
UserDataAuthClient::Shutdown();
AshTestBase::TearDown();
}
void InitializeUserManager() {
user_manager::UserManagerBase::RegisterPrefs(local_state_.registry());
user_manager_ =
std::make_unique<user_manager::FakeUserManager>(&local_state_);
user_manager_->Initialize();
}
void AddUserToUserManager() {
account_id_ = AccountId::FromUserEmail(kUserEmail);
const user_manager::User* user = user_manager_->AddUser(account_id_);
user_manager_->UserLoggedIn(account_id_, user->username_hash(), false,
false);
user_manager_->SetUserCryptohomeDataEphemeral(account_id_, false);
}
std::string HashPassword(const std::string& unhashed_password) {
Key key(std::move(unhashed_password));
key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
SystemSaltGetter::ConvertRawSaltToHexString(
FakeCryptohomeMiscClient::GetStubSystemSalt()));
return key.GetSecret();
}
std::string HashPin(const std::string& unhashed_pin) {
Key key(std::move(unhashed_pin));
key.Transform(Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234, kExpectedSalt);
return key.GetSecret();
}
void AddGaiaPassword(const AccountId& user, const std::string& password) {
auto account_identifier =
cryptohome::CreateAccountIdentifierFromAccountId(user);
FakeUserDataAuthClient::TestApi::Get()->AddExistingUser(account_identifier);
// Hash the password, as only hashed passwords appear at the userdataauth
// level.
Key key(HashPassword(password));
user_data_auth::AuthFactor auth_factor;
user_data_auth::AuthInput auth_input;
auth_factor.set_label(ash::kCryptohomeGaiaKeyLabel);
auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
auth_input.mutable_password_input()->set_secret(key.GetSecret());
// Add the password key to the user.
FakeUserDataAuthClient::TestApi::Get()->AddAuthFactor(
account_identifier, auth_factor, auth_input);
}
void AddCryptohomePin(const AccountId& user, const std::string& pin) {
auto account_identifier =
cryptohome::CreateAccountIdentifierFromAccountId(user);
// Hash the pin, as only hashed secrets appear at the userdataauth
// level.
Key key(HashPin(pin));
// Add the pin key to the user.
user_data_auth::AuthFactor auth_factor;
user_data_auth::AuthInput auth_input;
auth_factor.set_label(ash::kCryptohomePinLabel);
auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PIN);
auth_input.mutable_password_input()->set_secret(key.GetSecret());
FakeUserDataAuthClient::TestApi::Get()->AddAuthFactor(
account_identifier, auth_factor, auth_input);
}
protected:
AccountId account_id_;
TestingPrefServiceSimple local_state_;
std::unique_ptr<user_manager::FakeUserManager> user_manager_;
std::unique_ptr<AuthParts> auth_parts_;
};
// Tests that the StartAuthSession call to cryptohome includes the correct
// account id.
TEST_F(ActiveSessionAuthControllerTest,
StartAuthSessionCalledWithCorrectAccountId) {
AddGaiaPassword(account_id_, kExpectedPassword);
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(
FakeUserDataAuthClient::Get()
->WasCalled<FakeUserDataAuthClient::Operation::kStartAuthSession>());
auto start_auth_session_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kStartAuthSession>();
EXPECT_EQ(start_auth_session_request.account_id().account_id(), kUserEmail);
}
// Tests that the ListAuthFactors call to cryptohome includes the correct
// account id and returns the password factor.
TEST_F(ActiveSessionAuthControllerTest, ListAuthFactorsReturnsPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
base::RunLoop().RunUntilIdle();
AuthFactorSet available_factors =
ActiveSessionAuthControllerImpl::TestApi(controller)
.GetAvailableFactors();
EXPECT_TRUE(
FakeUserDataAuthClient::Get()
->WasCalled<FakeUserDataAuthClient::Operation::kListAuthFactors>());
auto list_auth_factors_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kListAuthFactors>();
EXPECT_EQ(list_auth_factors_request.account_id().account_id(), kUserEmail);
EXPECT_EQ(1u, available_factors.size());
EXPECT_TRUE(available_factors.Has(AuthInputType::kPassword));
}
// Tests that the ListAuthFactors call to cryptohome includes the correct
// account id and returns the password and pin factors.
TEST_F(ActiveSessionAuthControllerTest, ListAuthFactorsReturnsPasswordAndPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
AuthFactorSet available_factors =
ActiveSessionAuthControllerImpl::TestApi(controller)
.GetAvailableFactors();
EXPECT_TRUE(
FakeUserDataAuthClient::Get()
->WasCalled<FakeUserDataAuthClient::Operation::kListAuthFactors>());
auto list_auth_factors_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kListAuthFactors>();
EXPECT_EQ(list_auth_factors_request.account_id().account_id(), kUserEmail);
EXPECT_EQ(2u, available_factors.size());
EXPECT_TRUE(available_factors.Has(AuthInputType::kPassword));
EXPECT_TRUE(available_factors.Has(AuthInputType::kPin));
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and password, and that the `OnAuthComplete` callback
// is called with correct parameters.
TEST_F(ActiveSessionAuthControllerTest, SubmitPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller)
.SubmitPassword(kExpectedPassword);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(
authenticate_auth_factor_request.auth_input().password_input().secret(),
HashPassword(kExpectedPassword));
EXPECT_TRUE(future.IsReady());
EXPECT_EQ(future.Get<bool>(), true);
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and password, and that the `OnAuthComplete` callback
// is not called with wrong credentials.
TEST_F(ActiveSessionAuthControllerTest, WrongPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
FakeUserDataAuthClient::Get()->SetNextOperationError(
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor,
cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED));
ActiveSessionAuthControllerImpl::TestApi(controller)
.SubmitPassword(kExpectedPassword);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(
authenticate_auth_factor_request.auth_input().password_input().secret(),
HashPassword(kExpectedPassword));
EXPECT_FALSE(future.IsReady());
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and pin, and that the `OnAuthComplete` callback
// is called with the correct credentials.
TEST_F(ActiveSessionAuthControllerTest, SubmitPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller).SubmitPin(kExpectedPin);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(authenticate_auth_factor_request.auth_input().pin_input().secret(),
HashPin(kExpectedPin));
EXPECT_TRUE(future.IsReady());
EXPECT_EQ(future.Get<bool>(), true);
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and password, and that the `OnAuthComplete` callback
// is not called with wrong credentials.
TEST_F(ActiveSessionAuthControllerTest, WrongPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
FakeUserDataAuthClient::Get()->SetNextOperationError(
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor,
cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED));
ActiveSessionAuthControllerImpl::TestApi(controller).SubmitPin(kExpectedPin);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(authenticate_auth_factor_request.auth_input().pin_input().secret(),
HashPin(kExpectedPin));
EXPECT_FALSE(future.IsReady());
}
// Tests that the OnAuthCancel callback is called with the correct
// parameters.
TEST_F(ActiveSessionAuthControllerTest, OnAuthCancel) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
ActiveSessionAuthController::Reason::kSettings, future.GetCallback());
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller).Close();
// Await close.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(future.IsReady());
EXPECT_FALSE(future.Get<bool>());
EXPECT_EQ(future.Get<1>(), std::string{});
}
} // namespace ash

@ -166,6 +166,12 @@ void FakeUserManager::SetUserNonCryptohomeDataEphemeral(
}
}
void FakeUserManager::SetUserCryptohomeDataEphemeral(
const AccountId& account_id,
bool is_ephemeral) {
accounts_with_ephemeral_cryptohome_data_.insert({account_id, is_ephemeral});
}
void FakeUserManager::UserLoggedIn(const AccountId& account_id,
const std::string& username_hash,
bool browser_restart,
@ -325,6 +331,19 @@ bool FakeUserManager::IsUserNonCryptohomeDataEphemeral(
account_id);
}
bool FakeUserManager::IsUserCryptohomeDataEphemeral(
const AccountId& account_id) const {
auto is_ephemeral_overriden =
base::Contains(accounts_with_ephemeral_cryptohome_data_, account_id);
if (!is_ephemeral_overriden) {
// Otherwise fall back to default behavior.
return UserManagerBase::IsUserCryptohomeDataEphemeral(account_id);
}
return accounts_with_ephemeral_cryptohome_data_.at(account_id);
}
bool FakeUserManager::IsGuestSessionAllowed() const {
return true;
}

@ -55,6 +55,11 @@ class USER_MANAGER_EXPORT FakeUserManager : public UserManagerBase {
void SetUserNonCryptohomeDataEphemeral(const AccountId& account_id,
bool is_ephemeral);
// Subsequent calls to IsCurrentUserCryptohomeDataEphemeral for
// |account_id| will return |is_ephemeral|.
void SetUserCryptohomeDataEphemeral(const AccountId& account_id,
bool is_ephemeral);
// UserManager overrides.
const UserList& GetUsers() const override;
UserList GetUsersAllowedForMultiProfile() const override;
@ -106,6 +111,8 @@ class USER_MANAGER_EXPORT FakeUserManager : public UserManagerBase {
bool IsLoggedInAsStub() const override;
bool IsUserNonCryptohomeDataEphemeral(
const AccountId& account_id) const override;
bool IsUserCryptohomeDataEphemeral(
const AccountId& account_id) const override;
bool IsGuestSessionAllowed() const override;
bool IsGaiaUserAllowed(const User& user) const override;
bool IsUserAllowed(const User& user) const override;
@ -141,6 +148,10 @@ class USER_MANAGER_EXPORT FakeUserManager : public UserManagerBase {
// Contains AccountIds for which IsCurrentUserNonCryptohomeDataEphemeral will
// return true.
std::set<AccountId> accounts_with_ephemeral_non_cryptohome_data_;
// Contains AccountIds for which IsCurrentUserCryptohomeDataEphemeral will
// return the specific value.
base::flat_map<AccountId, bool> accounts_with_ephemeral_cryptohome_data_;
};
} // namespace user_manager