| // 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 |