blob: 0a1358059eff78065da05e3f1eb0aeaf4640f9a6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/ash/login/easy_unlock/easy_unlock_notification_controller.h"
#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
#include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.h"
#include "chrome/browser/ash/login/easy_unlock/smartlock_feature_usage_metrics.h"
#include "chrome/browser/ash/login/session/chrome_session_manager.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.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/dbus_thread_manager.h"
#include "chromeos/ash/components/multidevice/beacon_seed.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/proximity_auth/fake_lock_handler.h"
#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_api.pb.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/account_id/account_id.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/display/display.h"
#include "ui/display/test/test_screen.h"
#include "ui/display/util/display_util.h"
#include "ui/views/test/test_views_delegate.h"
namespace ash {
namespace {
using ::device::MockBluetoothAdapter;
using ::testing::Return;
struct SmartLockStateTestCase {
SmartLockState smart_lock_state;
EasyUnlockAuthEvent easy_unlock_auth_event;
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
smart_lock_auth_event_password_state;
bool should_be_valid_on_remote_auth_failure;
};
constexpr SmartLockStateTestCase kSmartLockStateTestCases[] = {
{SmartLockState::kInactive,
EasyUnlockAuthEvent::PASSWORD_ENTRY_SERVICE_NOT_ACTIVE,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kServiceNotActive,
true},
{SmartLockState::kDisabled,
EasyUnlockAuthEvent::PASSWORD_ENTRY_SERVICE_NOT_ACTIVE,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kServiceNotActive,
true},
{SmartLockState::kBluetoothDisabled,
EasyUnlockAuthEvent::PASSWORD_ENTRY_NO_BLUETOOTH,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::kNoBluetooth,
true},
{SmartLockState::kConnectingToPhone,
EasyUnlockAuthEvent::PASSWORD_ENTRY_BLUETOOTH_CONNECTING,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kBluetoothConnecting,
false},
{SmartLockState::kPhoneNotFound,
EasyUnlockAuthEvent::PASSWORD_ENTRY_NO_PHONE,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kCouldNotConnectToPhone,
false},
{SmartLockState::kPhoneNotAuthenticated,
EasyUnlockAuthEvent::PASSWORD_ENTRY_PHONE_NOT_AUTHENTICATED,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kNotAuthenticated,
false},
{SmartLockState::kPhoneFoundLockedAndProximate,
EasyUnlockAuthEvent::PASSWORD_ENTRY_PHONE_LOCKED,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::kPhoneLocked,
true},
{SmartLockState::kPhoneFoundUnlockedAndDistant,
EasyUnlockAuthEvent::PASSWORD_ENTRY_RSSI_TOO_LOW,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::kRssiTooLow,
false},
{SmartLockState::kPhoneFoundLockedAndDistant,
EasyUnlockAuthEvent::PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kPhoneLockedAndRssiTooLow,
false},
{SmartLockState::kPhoneAuthenticated,
EasyUnlockAuthEvent::PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kAuthenticatedPhone,
false},
{SmartLockState::kPhoneNotLockable,
EasyUnlockAuthEvent::PASSWORD_ENTRY_PHONE_NOT_LOCKABLE,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kPhoneNotLockable,
false},
{SmartLockState::kPrimaryUserAbsent,
EasyUnlockAuthEvent::PASSWORD_ENTRY_PRIMARY_USER_ABSENT,
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::
kPrimaryUserAbsent,
false}};
class MockEasyUnlockNotificationController
: public EasyUnlockNotificationController {
public:
MockEasyUnlockNotificationController()
: EasyUnlockNotificationController(nullptr) {}
MockEasyUnlockNotificationController(
const MockEasyUnlockNotificationController&) = delete;
MockEasyUnlockNotificationController& operator=(
const MockEasyUnlockNotificationController&) = delete;
~MockEasyUnlockNotificationController() override {}
// EasyUnlockNotificationController:
MOCK_METHOD0(ShowChromebookAddedNotification, void());
MOCK_METHOD0(ShowPairingChangeNotification, void());
MOCK_METHOD1(ShowPairingChangeAppliedNotification, void(const std::string&));
};
// Define a stub MultiDeviceSetupDialog because the base class has a protected
// constructor and destructor (which prevents usage of smart pointers).
class FakeMultiDeviceSetupDialog
: public multidevice_setup::MultiDeviceSetupDialog {
public:
FakeMultiDeviceSetupDialog() = default;
};
} // namespace
class EasyUnlockServiceRegularTest : public testing::Test {
public:
EasyUnlockServiceRegularTest(const EasyUnlockServiceRegularTest&) = delete;
EasyUnlockServiceRegularTest& operator=(const EasyUnlockServiceRegularTest&) =
delete;
protected:
EasyUnlockServiceRegularTest()
: test_local_device_(
multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("local device")
.SetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockClient,
multidevice::SoftwareFeatureState::kEnabled)
.SetBeaconSeeds(multidevice::FromCryptAuthSeedList(
std::vector<cryptauth::BeaconSeed>(4)))
.Build()),
test_remote_device_smart_lock_host_(
multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("local device")
.SetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockHost,
multidevice::SoftwareFeatureState::kEnabled)
.SetBeaconSeeds(multidevice::FromCryptAuthSeedList(
std::vector<cryptauth::BeaconSeed>(4)))
.Build()) {}
~EasyUnlockServiceRegularTest() override = default;
void SetUp() override {
display::Screen::SetScreenInstance(&test_screen_);
display::SetInternalDisplayIds({test_screen_.GetPrimaryDisplay().id()});
chromeos::PowerManagerClient::InitializeFake();
// Note: this is necessary because objects owned by EasyUnlockService
// depend on the BluetoothAdapter -- fetching the real one causes tests
// to fail.
mock_adapter_ = new testing::NiceMock<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
TestingBrowserProcess::GetGlobal()->SetLocalState(&local_pref_service_);
RegisterLocalState(local_pref_service_.registry());
auto test_other_remote_device =
multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("potential, but disabled, host device")
.SetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockHost,
multidevice::SoftwareFeatureState::kSupported)
.Build();
test_remote_devices_.push_back(test_remote_device_smart_lock_host_);
test_remote_devices_.push_back(test_other_remote_device);
fake_secure_channel_client_ =
std::make_unique<secure_channel::FakeSecureChannelClient>();
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
fake_multidevice_setup_client_ =
std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
TestingProfile::Builder builder;
profile_ = builder.Build();
account_id_ = AccountId::FromUserEmail(profile_->GetProfileUserName());
auto fake_chrome_user_manager = std::make_unique<FakeChromeUserManager>();
fake_chrome_user_manager_ = fake_chrome_user_manager.get();
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_chrome_user_manager));
SetPrimaryUserLoggedIn();
fake_lock_handler_ = std::make_unique<proximity_auth::FakeLockHandler>();
SetScreenLockState(false /* is_locked */);
}
void TearDown() override {
SetScreenLockState(false /* is_locked */);
easy_unlock_service_regular_->Shutdown();
chromeos::PowerManagerClient::Shutdown();
TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
display::Screen::SetScreenInstance(nullptr);
}
// Most tests will want to pass `should_initialize_all_dependencies` == true,
// but may pass false if they wish to tweak the dependencies' state themselves
// before initializing the service.
void InitializeService(bool should_initialize_all_dependencies) {
if (should_initialize_all_dependencies) {
fake_device_sync_client_->NotifyReady();
SetLocalDevice(test_local_device_);
SetSyncedDevices(test_remote_devices_);
SetIsEnabled(true /* is_enabled */);
}
auto mock_notification_controller = std::make_unique<
testing::StrictMock<MockEasyUnlockNotificationController>>();
mock_notification_controller_ = mock_notification_controller.get();
easy_unlock_service_regular_ = std::make_unique<EasyUnlockServiceRegular>(
profile_.get(), fake_secure_channel_client_.get(),
std::move(mock_notification_controller), fake_device_sync_client_.get(),
fake_multidevice_setup_client_.get());
easy_unlock_service_regular_->Initialize();
}
void SetLocalDevice(
const absl::optional<multidevice::RemoteDeviceRef>& local_device) {
fake_device_sync_client_->set_local_device_metadata(test_local_device_);
fake_device_sync_client_->NotifyEnrollmentFinished();
}
void SetSyncedDevices(multidevice::RemoteDeviceRefList synced_devices) {
fake_device_sync_client_->set_synced_devices(synced_devices);
fake_device_sync_client_->NotifyNewDevicesSynced();
}
void SetIsEnabled(bool is_enabled) {
fake_multidevice_setup_client_->SetFeatureState(
multidevice_setup::mojom::Feature::kSmartLock,
is_enabled ? multidevice_setup::mojom::FeatureState::kEnabledByUser
: multidevice_setup::mojom::FeatureState::kDisabledByUser);
}
void SetEasyUnlockAllowedPolicy(bool allowed) {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kEasyUnlockAllowed, std::make_unique<base::Value>(allowed));
}
void SetScreenLockState(bool is_locked) {
if (is_locked)
proximity_auth::ScreenlockBridge::Get()->SetFocusedUser(account_id_);
fake_lock_handler_->ClearSmartLockState();
fake_lock_handler_->ClearSmartLockAuthResult();
proximity_auth::ScreenlockBridge::Get()->SetLockHandler(
is_locked ? fake_lock_handler_.get() : nullptr);
}
void VerifyGetRemoteDevices(bool are_remote_devices_expected) {
const multidevice::RemoteDeviceRefList remote_devices =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get())
->GetRemoteDevicesForTesting();
if (are_remote_devices_expected) {
EXPECT_FALSE(remote_devices.empty());
} else {
EXPECT_TRUE(remote_devices.empty());
}
}
void SetDisplaySize(const gfx::Size& size) {
display::Display display = test_screen_.GetPrimaryDisplay();
display.SetSize(size);
test_screen_.display_list().RemoveDisplay(display.id());
test_screen_.display_list().AddDisplay(display,
display::DisplayList::Type::PRIMARY);
}
EasyUnlockAuthEvent GetPasswordAuthEvent() {
return easy_unlock_service_regular_->GetPasswordAuthEvent();
}
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState
GetSmartUnlockPasswordAuthEvent() {
return easy_unlock_service_regular_->GetSmartUnlockPasswordAuthEvent();
}
void ResetSmartLockState() {
easy_unlock_service_regular_->ResetSmartLockState();
}
// Must outlive TestingProfiles.
content::BrowserTaskEnvironment task_environment_;
// PrefService which contains the browser process' local storage. It should be
// destructed after TestingProfile.
TestingPrefServiceSimple local_pref_service_;
std::unique_ptr<TestingProfile> profile_;
AccountId account_id_;
raw_ptr<FakeChromeUserManager, ExperimentalAsh> fake_chrome_user_manager_;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
const multidevice::RemoteDeviceRef test_local_device_;
const multidevice::RemoteDeviceRef test_remote_device_smart_lock_host_;
multidevice::RemoteDeviceRefList test_remote_devices_;
std::unique_ptr<secure_channel::FakeSecureChannelClient>
fake_secure_channel_client_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
fake_multidevice_setup_client_;
std::unique_ptr<proximity_auth::FakeLockHandler> fake_lock_handler_;
std::unique_ptr<EasyUnlockServiceRegular> easy_unlock_service_regular_;
std::string profile_gaia_id_;
scoped_refptr<testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
raw_ptr<testing::StrictMock<MockEasyUnlockNotificationController>,
ExperimentalAsh>
mock_notification_controller_;
views::TestViewsDelegate view_delegate_;
base::HistogramTester histogram_tester_;
display::test::TestScreen test_screen_;
private:
void SetPrimaryUserLoggedIn() {
const user_manager::User* user =
fake_chrome_user_manager_->AddPublicAccountUser(account_id_);
fake_chrome_user_manager_->UserLoggedIn(account_id_, user->username_hash(),
false /* browser_restart */,
false /* is_child */);
}
};
TEST_F(EasyUnlockServiceRegularTest, NotAllowedWhenProhibited) {
InitializeService(true /* should_initialize_all_dependencies */);
fake_multidevice_setup_client_->SetFeatureState(
multidevice_setup::mojom::Feature::kSmartLock,
multidevice_setup::mojom::FeatureState::kProhibitedByPolicy);
EXPECT_FALSE(easy_unlock_service_regular_->IsAllowed());
}
TEST_F(EasyUnlockServiceRegularTest, NotAllowedForEphemeralAccounts) {
InitializeService(true /* should_initialize_all_dependencies */);
fake_chrome_user_manager_->set_current_user_ephemeral(true);
EXPECT_FALSE(easy_unlock_service_regular_->IsAllowed());
}
TEST_F(EasyUnlockServiceRegularTest, GetProximityAuthPrefManager) {
InitializeService(true /* should_initialize_all_dependencies */);
EXPECT_TRUE(
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get())
->GetProximityAuthPrefManager());
}
TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices) {
InitializeService(true /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
}
TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_InitiallyNotReady) {
SetIsEnabled(true /* is_enabled */);
InitializeService(false /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
EXPECT_CALL(*mock_notification_controller_,
ShowChromebookAddedNotification());
fake_device_sync_client_->NotifyReady();
SetLocalDevice(test_local_device_);
SetSyncedDevices(test_remote_devices_);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
}
TEST_F(EasyUnlockServiceRegularTest,
GetRemoteDevices_InitiallyNoSyncedDevices) {
fake_device_sync_client_->NotifyReady();
SetLocalDevice(test_local_device_);
SetSyncedDevices(multidevice::RemoteDeviceRefList() /* synced_devices */);
SetIsEnabled(true /* is_enabled */);
InitializeService(false /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
EXPECT_CALL(*mock_notification_controller_,
ShowChromebookAddedNotification());
SetSyncedDevices(test_remote_devices_);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
}
// Test that the "Chromebook added" notification does not show while the
// MultiDeviceSetupDialog is active, and only shows once it is closed.
TEST_F(
EasyUnlockServiceRegularTest,
GetRemoteDevices_InitiallyNoSyncedDevices_MultiDeviceSetupDialogVisible) {
SetDisplaySize(gfx::Size(1920, 1200));
ChromeSessionManager manager;
auto dialog = std::make_unique<FakeMultiDeviceSetupDialog>();
multidevice_setup::MultiDeviceSetupDialog::SetInstanceForTesting(
dialog.get());
fake_device_sync_client_->NotifyReady();
SetLocalDevice(test_local_device_);
SetSyncedDevices(multidevice::RemoteDeviceRefList() /* synced_devices */);
SetIsEnabled(true /* is_enabled */);
InitializeService(false /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
// Calling SetSyncedDevices() below would usually cause the "Chromebook added"
// notification to appear, but it shouldn't because MultiDeviceSetupDialog is
// active.
EXPECT_CALL(*mock_notification_controller_, ShowChromebookAddedNotification())
.Times(0);
SetSyncedDevices(test_remote_devices_);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
// Now expect the "Chromebook added" notification to appear, and close the
// dialog by deleting it (this indirectly calls the dialog close callbacks).
// Using the real dialog Close() method invokes Ash and Widget code that is
// too cumbersome to stub out.
testing::Mock::VerifyAndClearExpectations(mock_notification_controller_);
EXPECT_CALL(*mock_notification_controller_,
ShowChromebookAddedNotification());
dialog.reset();
multidevice_setup::MultiDeviceSetupDialog::SetInstanceForTesting(nullptr);
}
TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_InitiallyNotEnabled) {
fake_device_sync_client_->NotifyReady();
SetLocalDevice(test_local_device_);
SetSyncedDevices(test_remote_devices_);
SetIsEnabled(false /* is_enabled */);
InitializeService(false /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
SetIsEnabled(true /* is_enabled */);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
}
TEST_F(EasyUnlockServiceRegularTest,
GetRemoteDevices_DeferDeviceLoadUntilScreenIsUnlocked) {
SetScreenLockState(true /* is_locked */);
InitializeService(true /* should_initialize_all_dependencies */);
VerifyGetRemoteDevices(false /* are_remote_devices_expected */);
SetScreenLockState(false /* is_locked */);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
}
TEST_F(EasyUnlockServiceRegularTest, GetRemoteDevices_SmartLockHostChanged) {
InitializeService(true /* should_initialize_all_dependencies */);
SetScreenLockState(true /* is_locked */);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
auto new_remote_device =
multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("new smartlock host")
.SetSoftwareFeatureState(multidevice::SoftwareFeature::kSmartLockHost,
multidevice::SoftwareFeatureState::kEnabled)
.Build();
multidevice::RemoteDeviceRefList new_remote_devices;
new_remote_devices.push_back(new_remote_device);
EXPECT_CALL(*mock_notification_controller_, ShowPairingChangeNotification());
SetSyncedDevices(new_remote_devices /* synced_devices */);
VerifyGetRemoteDevices(true /* are_remote_devices_expected */);
EXPECT_CALL(*mock_notification_controller_,
ShowPairingChangeAppliedNotification(testing::_));
SetScreenLockState(false /* is_locked */);
}
// Test through the core flow of unlocking the screen with Smart Lock.
// Unfortunately, the only observable side effect we have available is verifying
// that the success metric is emitted.
// The "SmartLock.AuthResult" Failure bucket is incorrectly emitted to during
// this test. See crbug.com/1255964 for more info.
TEST_F(EasyUnlockServiceRegularTest, AuthenticateWithEasyUnlock) {
InitializeService(true /* should_initialize_all_dependencies */);
SetScreenLockState(true /* is_locked */);
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
// service->AttemptAuth() will fail if the SmartLockState is not
// kPhoneAuthenticated.
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_TRUE(service->AttemptAuth(account_id_));
service->FinalizeUnlock(true);
histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 0);
histogram_tester_.ExpectBucketCount(
"ChromeOS.FeatureUsage.SmartLock",
feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 0);
SetScreenLockState(false /* is_locked */);
histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 1);
histogram_tester_.ExpectBucketCount(
"ChromeOS.FeatureUsage.SmartLock",
feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 1);
}
// Regression test for crbug.com/974410.
// The "SmartLock.AuthResult" Failure bucket is incorrectly emitted to during
// this test. See crbug.com/1255964 for more info.
TEST_F(EasyUnlockServiceRegularTest, AuthenticateWithEasyUnlockMultipleTimes) {
InitializeService(true /* should_initialize_all_dependencies */);
SetScreenLockState(true /* is_locked */);
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
// service->AttemptAuth() will fail if the SmartLockState is not
// kPhoneAuthenticated.
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_TRUE(service->AttemptAuth(account_id_));
service->FinalizeUnlock(true);
// The first auth attempt is still ongoing. A second auth attempt request
// should be rejected.
EXPECT_FALSE(service->AttemptAuth(account_id_));
histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 0);
histogram_tester_.ExpectBucketCount(
"ChromeOS.FeatureUsage.SmartLock",
feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 0);
SetScreenLockState(false /* is_locked */);
histogram_tester_.ExpectBucketCount("SmartLock.AuthResult.Unlock", 1, 1);
histogram_tester_.ExpectBucketCount(
"ChromeOS.FeatureUsage.SmartLock",
feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 1);
}
// Regression test for b/213941298.
// Authenticate with Easy Unlock twice, calling UpdateSmartLockState in between.
// Ordinarily, this call to UpdateSmartLockState would be made by
// ProximityAuthClient during the OnScreenDidUnlock event. Check that the second
// call to AttemptAuth succeeds since UpdateSmartLockState clears out the last
// auth attempt, allowing the next auth attempt to proceed.
TEST_F(EasyUnlockServiceRegularTest,
UpdateSmartLockStateClearsLastAuthAttempt) {
InitializeService(true /* should_initialize_all_dependencies */);
SetScreenLockState(true /* is_locked */);
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_TRUE(service->AttemptAuth(account_id_));
service->FinalizeUnlock(true);
ASSERT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
SetScreenLockState(false /* is_locked */);
service->UpdateSmartLockState(SmartLockState::kInactive);
EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_FALSE(fake_lock_handler_->smart_lock_state().has_value());
SetScreenLockState(true /* is_locked */);
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_TRUE(service->AttemptAuth(account_id_));
service->FinalizeUnlock(true);
ASSERT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
SetScreenLockState(false /* is_locked */);
}
TEST_F(EasyUnlockServiceRegularTest, GetInitialSmartLockState_FeatureEnabled) {
InitializeService(true /* should_initialize_all_dependencies */);
SetScreenLockState(true /* is_locked */);
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
EXPECT_EQ(SmartLockState::kConnectingToPhone,
service->GetInitialSmartLockState());
}
TEST_F(EasyUnlockServiceRegularTest, GetInitialSmartLockState_FeatureDisabled) {
InitializeService(true /* should_initialize_all_dependencies */);
SetIsEnabled(false);
SetScreenLockState(true /* is_locked */);
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
EXPECT_EQ(SmartLockState::kDisabled, service->GetInitialSmartLockState());
}
TEST_F(EasyUnlockServiceRegularTest, ShowInitialSmartLockState_FeatureEnabled) {
InitializeService(true /* should_initialize_all_dependencies */);
EXPECT_FALSE(fake_lock_handler_->smart_lock_state());
SetScreenLockState(true /* is_locked */);
// Before EasyUnlockService::UpdateSmartLockState() is called externally,
// ensure that internal state is updated to an "initial" state to prevent
// UI jank.
EXPECT_EQ(SmartLockState::kConnectingToPhone,
fake_lock_handler_->smart_lock_state().value());
auto* service =
static_cast<EasyUnlockService*>(easy_unlock_service_regular_.get());
service->UpdateSmartLockState(SmartLockState::kConnectingToPhone);
EXPECT_EQ(SmartLockState::kConnectingToPhone,
fake_lock_handler_->smart_lock_state().value());
}
TEST_F(EasyUnlockServiceRegularTest,
ShowInitialSmartLockState_FeatureDisabled) {
InitializeService(true /* should_initialize_all_dependencies */);
SetIsEnabled(false);
EXPECT_FALSE(fake_lock_handler_->smart_lock_state());
SetScreenLockState(true /* is_locked */);
// Before EasyUnlockService::UpdateSmartLockState() is called externally,
// ensure that internal state is updated to an "initial" state to prevent
// UI jank.
EXPECT_EQ(SmartLockState::kDisabled,
fake_lock_handler_->smart_lock_state().value());
}
TEST_F(EasyUnlockServiceRegularTest, PrepareForSuspend) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_EQ(SmartLockState::kPhoneAuthenticated,
fake_lock_handler_->smart_lock_state().value());
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent::LID_CLOSED);
EXPECT_EQ(SmartLockState::kConnectingToPhone,
fake_lock_handler_->smart_lock_state().value());
}
TEST_F(EasyUnlockServiceRegularTest, HandleAuthFailureInUpdateSmartLockState) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
EXPECT_EQ(proximity_auth::mojom::AuthType::USER_CLICK,
fake_lock_handler_->GetAuthType(account_id_));
service->AttemptAuth(account_id_);
service->FinalizeUnlock(true);
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().value());
service->UpdateSmartLockState(SmartLockState::kPhoneNotAuthenticated);
EXPECT_EQ(proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
fake_lock_handler_->GetAuthType(account_id_));
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().value());
}
TEST_F(EasyUnlockServiceRegularTest, IsSmartLockStateValidOnRemoteAuthFailure) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
fake_lock_handler_->ClearSmartLockState();
fake_lock_handler_->ClearSmartLockAuthResult();
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
service->AttemptAuth(account_id_);
service->UpdateSmartLockState(testcase.smart_lock_state);
if (testcase.smart_lock_state != SmartLockState::kPhoneAuthenticated) {
EXPECT_EQ(proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
fake_lock_handler_->GetAuthType(account_id_));
if (testcase.should_be_valid_on_remote_auth_failure) {
EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().has_value());
} else {
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().value());
}
}
}
}
TEST_F(EasyUnlockServiceRegularTest, FinalizeUnlock) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
service->FinalizeUnlock(true);
EXPECT_EQ(0, fake_lock_handler_->unlock_called());
service->UpdateSmartLockState(SmartLockState::kPhoneAuthenticated);
service->AttemptAuth(account_id_);
service->FinalizeUnlock(true);
EXPECT_EQ(1, fake_lock_handler_->unlock_called());
service->FinalizeUnlock(false);
EXPECT_TRUE(fake_lock_handler_->smart_lock_auth_result().has_value());
EXPECT_FALSE(fake_lock_handler_->smart_lock_auth_result().value());
}
TEST_F(EasyUnlockServiceRegularTest, GetPasswordAuthEvent) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
ResetSmartLockState();
EXPECT_EQ(EasyUnlockAuthEvent::PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER,
GetPasswordAuthEvent());
for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
fake_lock_handler_->ClearSmartLockState();
fake_lock_handler_->ClearSmartLockAuthResult();
service->UpdateSmartLockState(testcase.smart_lock_state);
EXPECT_EQ(testcase.easy_unlock_auth_event, GetPasswordAuthEvent());
}
}
TEST_F(EasyUnlockServiceRegularTest, GetSmartUnlockPasswordAuthEvent) {
InitializeService(/*should_initialize_all_dependencies=*/true);
SetScreenLockState(/*is_locked=*/true);
EasyUnlockService* service = easy_unlock_service_regular_.get();
ResetSmartLockState();
EXPECT_EQ(
SmartLockMetricsRecorder::SmartLockAuthEventPasswordState::kUnknownState,
GetSmartUnlockPasswordAuthEvent());
for (const SmartLockStateTestCase& testcase : kSmartLockStateTestCases) {
fake_lock_handler_->ClearSmartLockState();
fake_lock_handler_->ClearSmartLockAuthResult();
service->UpdateSmartLockState(testcase.smart_lock_state);
EXPECT_EQ(testcase.smart_lock_auth_event_password_state,
GetSmartUnlockPasswordAuthEvent());
}
}
} // namespace ash