blob: 1d9856fdf85c577c6863f965b3967f2c85876fe3 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/login_screen_test_api.h"
#include "ash/shell.h"
#include "base/auto_reset.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/auth_ui_utils.h"
#include "chrome/browser/ash/login/test/cryptohome_mixin.h"
#include "chrome/browser/ash/login/test/fake_recovery_service_mixin.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/oobe_window_visibility_waiter.h"
#include "chrome/browser/ash/login/test/user_auth_config.h"
#include "chrome/browser/ash/login/wizard_context.h"
#include "chrome/browser/browser_process.h"
#include "chrome/test/base/fake_gaia_mixin.h"
#include "chromeos/ash/components/osauth/public/common_types.h"
#include "components/user_manager/known_user.h"
#include "content/public/test/browser_test.h"
namespace ash {
using test::UserAuthConfig;
// Base class for UI testing of various authentication that can
// take place at the login screen.
class AuthFlowsLoginTestBase : public LoginManagerTest {
public:
explicit AuthFlowsLoginTestBase(bool require_reauth)
: with_gaia_pw_{LoginManagerMixin::CreateConsumerAccountId(1),
UserAuthConfig::Create({AshAuthFactor::kGaiaPassword})
.RequireReauth(require_reauth)},
with_gaia_pw_recovery_{
LoginManagerMixin::CreateConsumerAccountId(2),
UserAuthConfig::Create(
{AshAuthFactor::kGaiaPassword, AshAuthFactor::kRecovery})
.RequireReauth(require_reauth)},
with_local_pw_{LoginManagerMixin::CreateConsumerAccountId(3),
UserAuthConfig::Create({AshAuthFactor::kLocalPassword})
.RequireReauth(require_reauth)},
with_local_pw_recovery_{
LoginManagerMixin::CreateConsumerAccountId(4),
UserAuthConfig::Create(
{AshAuthFactor::kLocalPassword, AshAuthFactor::kRecovery})
.RequireReauth(require_reauth)},
with_local_pw_pin_{
LoginManagerMixin::CreateConsumerAccountId(5),
UserAuthConfig::Create(
{AshAuthFactor::kLocalPassword, AshAuthFactor::kCryptohomePin})
.RequireReauth(require_reauth)},
with_local_pw_pin_recovery_{
LoginManagerMixin::CreateConsumerAccountId(6),
UserAuthConfig::Create({AshAuthFactor::kLocalPassword,
AshAuthFactor::kCryptohomePin,
AshAuthFactor::kRecovery})
.RequireReauth(require_reauth)},
with_pin_recovery_{
LoginManagerMixin::CreateConsumerAccountId(7),
UserAuthConfig::Create(
{AshAuthFactor::kCryptohomePin, AshAuthFactor::kRecovery})
.RequireReauth(require_reauth)},
with_pin_{LoginManagerMixin::CreateConsumerAccountId(8),
UserAuthConfig::Create({AshAuthFactor::kCryptohomePin})
.RequireReauth(require_reauth)},
login_mixin_{
&mixin_host_,
{with_gaia_pw_, with_gaia_pw_recovery_, with_local_pw_,
with_local_pw_recovery_, with_local_pw_pin_,
with_local_pw_pin_recovery_, with_pin_recovery_, with_pin_},
&fake_gaia_,
&cryptohome_}
{
SetHardwareSupportForPinLogin(true);
}
~AuthFlowsLoginTestBase() override = default;
void SetUpOnMainThread() override {
// Make `FakeUserDataAuthClient` perform actual password checks when
// handling authentication requests.
FakeUserDataAuthClient::TestApi::Get()->set_enable_auth_check(true);
LoginManagerTest::SetUpOnMainThread();
}
void ConfigureFakeGaiaFor(const LoginManagerMixin::TestUserInfo& user) {
fake_gaia_.SetupFakeGaiaForLogin(user.account_id.GetUserEmail(),
user.account_id.GetGaiaId(),
FakeGaiaMixin::kFakeRefreshToken);
}
// This must be called very early (e.g. in the constructor) so that the
// hardware support flag before `PinSetupScreen` reads it.
static void SetHardwareSupportForPinLogin(bool is_supported) {
FakeUserDataAuthClient::TestApi::Get()
->set_supports_low_entropy_credentials(is_supported);
}
protected:
const LoginManagerMixin::TestUserInfo with_gaia_pw_;
const LoginManagerMixin::TestUserInfo with_gaia_pw_recovery_;
const LoginManagerMixin::TestUserInfo with_local_pw_;
const LoginManagerMixin::TestUserInfo with_local_pw_recovery_;
const LoginManagerMixin::TestUserInfo with_local_pw_pin_;
const LoginManagerMixin::TestUserInfo with_local_pw_pin_recovery_;
const LoginManagerMixin::TestUserInfo with_pin_recovery_;
const LoginManagerMixin::TestUserInfo with_pin_;
CryptohomeMixin cryptohome_{&mixin_host_};
FakeGaiaMixin fake_gaia_{&mixin_host_};
base::AutoReset<bool> branded_build{&WizardContext::g_is_branded_build, true};
LoginManagerMixin login_mixin_;
FakeRecoveryServiceMixin fake_recovery_service_{&mixin_host_,
embedded_test_server()};
};
// ----------------------------------------------------------
class AuthFlowsLoginReauthTest : public AuthFlowsLoginTestBase {
public:
AuthFlowsLoginReauthTest()
: AuthFlowsLoginTestBase(/* require_reauth */ true) {
}
~AuthFlowsLoginReauthTest() override = default;
void TriggerUserOnlineAuth(const LoginManagerMixin::TestUserInfo user,
const std::string& password) {
ConfigureFakeGaiaFor(user);
test::OnLoginScreen()->SelectUserPod(user.account_id);
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(password);
gaia->ContinueLogin();
}
};
// ----------------------------------------------------------
class AuthFlowsLoginReauthWithPinTest : public AuthFlowsLoginReauthTest {
public:
AuthFlowsLoginReauthWithPinTest() : AuthFlowsLoginReauthTest() {
scoped_features_.InitAndEnableFeature(
features::kLocalAuthenticationWithPin);
}
~AuthFlowsLoginReauthWithPinTest() override = default;
base::test::ScopedFeatureList scoped_features_;
};
// ----------------------------------------------------------
class AuthFlowsLoginRecoverUserTest : public AuthFlowsLoginTestBase {
public:
AuthFlowsLoginRecoverUserTest()
: AuthFlowsLoginTestBase(/* require_reauth */ false) {
}
~AuthFlowsLoginRecoverUserTest() override = default;
void TriggerRecoveryOnAuthErrorBubble(
const LoginManagerMixin::TestUserInfo user) {
ConfigureFakeGaiaFor(user);
test::OnLoginScreen()->SelectUserPod(user.account_id);
test::OnLoginScreen()->SubmitPassword(user.account_id,
test::kWrongPassword);
auto auth_error_bubble = test::OnLoginScreen()->WaitForAuthErrorBubble();
auth_error_bubble->PressRecoveryButton();
test::AuthErrorBubbleDismissWaiter()->Wait();
}
void TriggerUserOnlineAuth(const LoginManagerMixin::TestUserInfo user,
const std::string& password) {
TriggerRecoveryOnAuthErrorBubble(user);
// Use gaia to authenticate for recovery.
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(password);
gaia->ContinueLogin();
}
};
// ----------------------------------------------------------
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest, GaiaPasswordNotChanged) {
const auto& user = with_gaia_pw_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest,
GaiaPasswordRecoveryNotChanged) {
const auto& user = with_gaia_pw_recovery_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest, GaiaPasswordChangedRecovery) {
const auto& user = with_gaia_pw_recovery_;
TriggerUserOnlineAuth(user, test::kNewPassword);
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest, GaiaPasswordChangedManual) {
const auto& user = with_gaia_pw_;
TriggerUserOnlineAuth(user, test::kNewPassword);
auto pw_changed = test::AwaitPasswordChangedUI();
pw_changed->TypePreviousPassword(test::kGaiaPassword);
pw_changed->SubmitPreviousPassword();
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
// ----------------------------------------------------------
class AuthFlowsLoginAddExistingUserTest : public AuthFlowsLoginTestBase {
public:
AuthFlowsLoginAddExistingUserTest()
: AuthFlowsLoginTestBase(/* require_reauth */ false) {}
~AuthFlowsLoginAddExistingUserTest() override = default;
void TriggerUserOnlineAuth(const LoginManagerMixin::TestUserInfo user,
const std::string& password) {
ConfigureFakeGaiaFor(user);
test::OnLoginScreen()->AddNewUser();
auto new_user = test::AwaitNewUserSelectionUI();
new_user->ChooseConsumerUser();
new_user->AwaitNextButton();
new_user->Next();
auto gaia = test::AwaitGaiaSigninUI();
gaia->SubmitFullAuthEmail(user.account_id);
gaia->TypePassword(password);
gaia->ContinueLogin();
}
void ExpectReauthForRapt(const LoginManagerMixin::TestUserInfo user,
const std::string& password) {
auto reauth = test::AwaitRecoveryReauthUI();
reauth->ConfirmReauth();
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(password);
gaia->ContinueLogin();
}
};
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginAddExistingUserTest,
GaiaPasswordNotChanged) {
const auto& user = with_gaia_pw_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginAddExistingUserTest,
GaiaPassworRecoverydNotChanged) {
const auto& user = with_gaia_pw_recovery_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginAddExistingUserTest,
GaiaPasswordChangedRecovery) {
const auto& user = with_gaia_pw_recovery_;
TriggerUserOnlineAuth(user, test::kNewPassword);
// Add new user flow does not provide RAPT token required for recovery.
// Expect a request to reauthenticate and proceed with recovery.
ExpectReauthForRapt(user, test::kNewPassword);
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginAddExistingUserTest,
GaiaPasswordChangedManual) {
const auto& user = with_gaia_pw_;
TriggerUserOnlineAuth(user, test::kNewPassword);
auto pw_changed = test::AwaitPasswordChangedUI();
pw_changed->TypePreviousPassword(test::kGaiaPassword);
pw_changed->SubmitPreviousPassword();
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
// ----------------------------------------------------------
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthWithPinTest,
GaiaPasswordChangedWithRecoveryPin) {
const auto& user = with_local_pw_pin_recovery_;
test::OnLoginScreen()->SelectUserPod(user.account_id);
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(test::kNewPassword);
gaia->ContinueLogin();
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->SubmitPin(test::kAuthPin);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthWithPinTest, PinCorrectPassword) {
const auto& user = with_local_pw_pin_;
test::OnLoginScreen()->SelectUserPod(user.account_id);
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(test::kNewPassword);
gaia->ContinueLogin();
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->SubmitPin(test::kAuthPin);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthWithPinTest,
CancelLocalAuthenticationDialog) {
const auto& user = with_local_pw_pin_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->CancelDialog();
local_authentication->WaitUntilDismissed();
// After dismissing the local authentication dialog,
// the flow should return to and display the login screen.
test::OnLoginScreen()->SelectUserPod(user.account_id);
}
// ----------------------------------------------------------
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest,
GaiaPasswordChangedWithRecoveryLocalPassword) {
const auto& user = with_local_pw_recovery_;
test::OnLoginScreen()->SelectUserPod(user.account_id);
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(test::kNewPassword);
gaia->ContinueLogin();
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->SubmitPassword(test::kLocalPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest, LocalPasswordCorrectPassword) {
const auto& user = with_local_pw_;
test::OnLoginScreen()->SelectUserPod(user.account_id);
auto gaia = test::AwaitGaiaSigninUI();
gaia->ReauthConfirmEmail(user.account_id);
gaia->TypePassword(test::kNewPassword);
gaia->ContinueLogin();
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->SubmitPassword(test::kLocalPassword);
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest,
CancelLocalAuthenticationDialog) {
const auto& user = with_local_pw_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
auto local_authentication =
test::OnLoginScreen()->WaitForLocalAuthenticationDialog();
local_authentication->CancelDialog();
local_authentication->WaitUntilDismissed();
// After dismissing the local authentication dialog,
// the flow should return to and display the login screen.
test::OnLoginScreen()->SelectUserPod(user.account_id);
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginReauthTest, AuthenticateWithRecovery) {
const auto& user = with_local_pw_recovery_;
TriggerUserOnlineAuth(user, test::kGaiaPassword);
// After successful GAIA authentication in recovery session,
// the session should start automatically.
login_mixin_.WaitForActiveSession();
}
// ----------------------------------------------------------
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
LocalPasswordWithRecovery) {
const auto& user = with_local_pw_recovery_;
TriggerUserOnlineAuth(user, FakeGaiaMixin::kFakeUserPassword);
// Set up a new password.
auto local_password_setup = test::AwaitLocalPasswordSetupUI();
local_password_setup->TypeFirstPassword(test::kNewPassword);
local_password_setup->TypeConfirmPassword(test::kNewPassword);
local_password_setup->Submit();
// Recovery password update confirmation.
test::RecoveryPasswordUpdatedPageWaiter()->Wait();
test::RecoveryPasswordUpdatedProceedAction();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
LocalPasswordWithoutRecoveryCancelLAD) {
const auto& user = with_local_pw_;
// Start recovery flow without recovery auth factor.
TriggerUserOnlineAuth(user, FakeGaiaMixin::kFakeUserPassword);
// Wait for local data loss warning.
test::LocalDataLossWarningPageWaiter()->Wait();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
GaiaPasswordWithRecovery) {
const auto& user = with_gaia_pw_recovery_;
// Start recovery flow with recovery auth factor.
TriggerUserOnlineAuth(user, test::kNewPassword);
// Wait for password update dialog.
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
GaiaPasswordWithoutRecovery) {
const auto& user = with_gaia_pw_;
// Start recovery flow without recovery auth factor.
TriggerUserOnlineAuth(user, test::kWrongPassword);
auto pw_changed = test::AwaitPasswordChangedUI();
pw_changed->TypePreviousPassword(test::kGaiaPassword);
pw_changed->SubmitPreviousPassword();
// Wait for password update dialog.
auto pw_updated = test::AwaitPasswordUpdatedUI();
pw_updated->ExpectPasswordUpdateState();
pw_updated->ConfirmPasswordUpdate();
login_mixin_.WaitForActiveSession();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
GaiaPasswordWithoutRecoveryInvalidPassword) {
const auto& user = with_gaia_pw_;
// Start recovery flow without recovery auth factor.
TriggerUserOnlineAuth(user, test::kWrongPassword);
auto pw_changed = test::AwaitPasswordChangedUI();
pw_changed->TypePreviousPassword(test::kWrongPassword);
pw_changed->SubmitPreviousPassword();
// Keep user on passwordChanged screen after incorrect password, display error
// message.
pw_changed->InvalidPasswordFeedback()->Wait();
}
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTest,
GaiaPasswordWithoutRecoveryForgotPasswordClick) {
const auto& user = with_gaia_pw_;
// Start recovery flow without recovery auth factor.
TriggerUserOnlineAuth(user, test::kWrongPassword);
auto pw_changed = test::AwaitPasswordChangedUI();
// Clicks on forgot password button.
pw_changed->ForgotPreviousPassword();
// Wait for local data loss warning.
test::LocalDataLossWarningPageWaiter()->Wait();
}
class AuthFlowsLoginRecoverUserTestPasswordlessRecovery
: public AuthFlowsLoginRecoverUserTest {
public:
AuthFlowsLoginRecoverUserTestPasswordlessRecovery() {
feature_list_.InitAndEnableFeature(
ash::features::kAllowPasswordlessRecovery);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Ensures that a user with PIN-only (without recovery) is shown the local data
// loss warning screen.
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTestPasswordlessRecovery,
PinOnlyWithoutRecovery) {
const auto& user = with_pin_;
// Start recovery flow without recovery auth factor.
TriggerUserOnlineAuth(user, FakeGaiaMixin::kFakeUserPassword);
// Wait for local data loss warning.
test::LocalDataLossWarningPageWaiter()->Wait();
}
// Ensures that going through recovery for the PIN-only scenario works.
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTestPasswordlessRecovery,
RecoveryWithPinOnly) {
const auto& user = with_pin_recovery_;
// Start recovery flow with recovery auth factor.
TriggerUserOnlineAuth(user, FakeGaiaMixin::kFakeUserPassword);
auto pin_setup = test::AwaitPinSetupUI();
pin_setup->InsertAndConfirmPin("123456");
pin_setup->TapDone();
login_mixin_.WaitForActiveSession();
}
// Checks that the PIN autosubmit length is properly updated.
IN_PROC_BROWSER_TEST_F(AuthFlowsLoginRecoverUserTestPasswordlessRecovery,
AutoSubmitLengthIsCorrectlyUpdated) {
const auto& user = with_pin_recovery_;
// Set initial fictional start value to be '6'.
user_manager::KnownUser known_user(g_browser_process->local_state());
known_user.SetUserPinLength(user.account_id, 6);
// Use recovery to set a new PIN that is 8 digits long.
TriggerUserOnlineAuth(user, FakeGaiaMixin::kFakeUserPassword);
auto pin_setup = test::AwaitPinSetupUI();
pin_setup->InsertAndConfirmPin("12345678");
pin_setup->TapDone();
login_mixin_.WaitForActiveSession();
EXPECT_EQ(known_user.GetUserPinLength(user.account_id), 8);
}
} // namespace ash