diff options
author | isherman <isherman@chromium.org> | 2015-08-14 19:47:59 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-15 02:48:41 +0000 |
commit | 6e9846255dbb5b9bcf572536b757e998d6471117 (patch) | |
tree | f3f307677c073de5be1c9e45fd11f6813d972abf /components/proximity_auth | |
parent | 7458fb3730f7cfc084f310a7cbebde489aa78439 (diff) | |
download | chromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.zip chromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.tar.gz chromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.tar.bz2 |
[Proximity Auth] Port the UnlockManager class.
This ports most of the code, with a few TODOs remaining for future CLs.
BUG=501626
R=tengs@chromium.org
Review URL: https://codereview.chromium.org/1239193005
Cr-Commit-Position: refs/heads/master@{#343537}
Diffstat (limited to 'components/proximity_auth')
-rw-r--r-- | components/proximity_auth/BUILD.gn | 4 | ||||
-rw-r--r-- | components/proximity_auth/DEPS | 2 | ||||
-rw-r--r-- | components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc | 5 | ||||
-rw-r--r-- | components/proximity_auth/controller.h | 34 | ||||
-rw-r--r-- | components/proximity_auth/metrics.cc | 8 | ||||
-rw-r--r-- | components/proximity_auth/metrics.h | 17 | ||||
-rw-r--r-- | components/proximity_auth/proximity_auth_client.h | 11 | ||||
-rw-r--r-- | components/proximity_auth/proximity_monitor_impl_unittest.cc | 1 | ||||
-rw-r--r-- | components/proximity_auth/unlock_manager.cc | 467 | ||||
-rw-r--r-- | components/proximity_auth/unlock_manager.h | 197 | ||||
-rw-r--r-- | components/proximity_auth/unlock_manager_unittest.cc | 814 |
11 files changed, 1557 insertions, 3 deletions
diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn index 4541e8c..5c45881 100644 --- a/components/proximity_auth/BUILD.gn +++ b/components/proximity_auth/BUILD.gn @@ -25,6 +25,7 @@ source_set("proximity_auth") { "connection.h", "connection_finder.h", "connection_observer.h", + "controller.h", "device_to_device_authenticator.cc", "device_to_device_authenticator.h", "device_to_device_initiator_operations.cc", @@ -52,6 +53,8 @@ source_set("proximity_auth") { "switches.h", "throttled_bluetooth_connection_finder.cc", "throttled_bluetooth_connection_finder.h", + "unlock_manager.cc", + "unlock_manager.h", "wire_message.cc", "wire_message.h", ] @@ -94,6 +97,7 @@ source_set("unit_tests") { "proximity_monitor_impl_unittest.cc", "remote_status_update_unittest.cc", "throttled_bluetooth_connection_finder_unittest.cc", + "unlock_manager_unittest.cc", "wire_message_unittest.cc", ] diff --git a/components/proximity_auth/DEPS b/components/proximity_auth/DEPS index 161da31..24229c6 100644 --- a/components/proximity_auth/DEPS +++ b/components/proximity_auth/DEPS @@ -4,7 +4,7 @@ include_rules = [ ] specific_include_rules = { - "screenlock_bridge\.*": [ + "screenlock_bridge\.*|unlock_manager\.*": [ "+chromeos", ], } diff --git a/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc b/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc index e178a0b..79fd6ef 100644 --- a/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc +++ b/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc @@ -71,7 +71,10 @@ class MockProximityAuthClient : public ProximityAuthClient { // ProximityAuthClient: std::string GetAuthenticatedUsername() const override { return kTestUser; } - MOCK_METHOD1(FinalizeUnlock, void(bool)); + MOCK_METHOD1(UpdateScreenlockState, + void(proximity_auth::ScreenlockState state)); + MOCK_METHOD1(FinalizeUnlock, void(bool success)); + MOCK_METHOD1(FinalizeSignin, void(const std::string& secret)); private: DISALLOW_COPY_AND_ASSIGN(MockProximityAuthClient); diff --git a/components/proximity_auth/controller.h b/components/proximity_auth/controller.h new file mode 100644 index 0000000..4261e78 --- /dev/null +++ b/components/proximity_auth/controller.h @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H +#define COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H + +#include "base/macros.h" + +namespace proximity_auth { + +class Client; + +// Main class that controls the logic flow of the Easy Unlock system. +class Controller { + public: + enum class State { + NOT_STARTED, + FINDING_CONNECTION, + AUTHENTICATING, + SECURE_CHANNEL_ESTABLISHED, + AUTHENTICATION_FAILED, + STOPPED, + }; + + virtual ~Controller() {} + + virtual State GetState() const = 0; + virtual Client* GetClient() = 0; +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H diff --git a/components/proximity_auth/metrics.cc b/components/proximity_auth/metrics.cc index 99f74b1..7b4db8f 100644 --- a/components/proximity_auth/metrics.cc +++ b/components/proximity_auth/metrics.cc @@ -4,6 +4,7 @@ #include "components/proximity_auth/metrics.h" +#include "base/logging.h" #include "base/md5.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/sparse_histogram.h" @@ -69,5 +70,12 @@ void RecordAuthProximityRemoteDeviceModelHash(const std::string& device_model) { HashDeviceModelName(device_model)); } +void RecordRemoteSecuritySettingsState(RemoteSecuritySettingsState state) { + DCHECK(state < RemoteSecuritySettingsState::COUNT); + UMA_HISTOGRAM_ENUMERATION( + "EasyUnlock.RemoteLockScreenState", static_cast<int>(state), + static_cast<int>(RemoteSecuritySettingsState::COUNT)); +} + } // namespace metrics } // namespace proximity_auth diff --git a/components/proximity_auth/metrics.h b/components/proximity_auth/metrics.h index 5a0e20a..a1ac3cd 100644 --- a/components/proximity_auth/metrics.h +++ b/components/proximity_auth/metrics.h @@ -13,6 +13,19 @@ namespace metrics { extern const char kUnknownDeviceModel[]; extern const int kUnknownProximityValue; +// Possible states of the remote device's security settings. This enum is used +// to back a histogram, and hence should be treated as append-only. +enum class RemoteSecuritySettingsState { + UNKNOWN, + SCREEN_LOCK_DISABLED_TRUST_AGENT_UNSUPPORTED, + SCREEN_LOCK_DISABLED_TRUST_AGENT_DISABLED, + SCREEN_LOCK_DISABLED_TRUST_AGENT_ENABLED, + SCREEN_LOCK_ENABLED_TRUST_AGENT_UNSUPPORTED, + SCREEN_LOCK_ENABLED_TRUST_AGENT_DISABLED, + SCREEN_LOCK_ENABLED_TRUST_AGENT_ENABLED, + COUNT +}; + // Records the current |rolling_rssi| reading, upon a successful auth attempt. // |rolling_rssi| should be set to |kUnknownProximityValue| if no RSSI readings // are available. @@ -33,6 +46,10 @@ void RecordAuthProximityTimeSinceLastZeroRssi( // set to |kUnknownDeviceModel| if the device model could not be read. void RecordAuthProximityRemoteDeviceModelHash(const std::string& device_model); +// Records the screen lock and trust agent settings state of the remote device, +// as received in a status update from the remote device. +void RecordRemoteSecuritySettingsState(RemoteSecuritySettingsState state); + } // namespace metrics } // namespace proximity_auth diff --git a/components/proximity_auth/proximity_auth_client.h b/components/proximity_auth/proximity_auth_client.h index eb846cd..b5d2c7b 100644 --- a/components/proximity_auth/proximity_auth_client.h +++ b/components/proximity_auth/proximity_auth_client.h @@ -7,6 +7,8 @@ #include <string> +#include "components/proximity_auth/screenlock_state.h" + namespace proximity_auth { // An interface that needs to be supplied to the Proximity Auth component by its @@ -19,10 +21,19 @@ class ProximityAuthClient { // Returns the authenticated username. virtual std::string GetAuthenticatedUsername() const = 0; + // Updates the user pod on the signin or lock screen to reflect the provided + // screenlock state. + virtual void UpdateScreenlockState(ScreenlockState state) = 0; + // Finalizes an unlock attempt initiated by the user. If |success| is true, // the screen is unlocked; otherwise, the auth attempt is rejected. An auth // attempt must be in progress before calling this function. virtual void FinalizeUnlock(bool success) = 0; + + // Finalizes a sign-in attempt initiated by the user. If |success| is true, + // the user is signed in; otherwise, the auth attempt is rejected. An auth + // attempt must be in progress before calling this function. + virtual void FinalizeSignin(const std::string& secret) = 0; }; } // namespace proximity_auth diff --git a/components/proximity_auth/proximity_monitor_impl_unittest.cc b/components/proximity_auth/proximity_monitor_impl_unittest.cc index 4231934..12f74ed 100644 --- a/components/proximity_auth/proximity_monitor_impl_unittest.cc +++ b/components/proximity_auth/proximity_monitor_impl_unittest.cc @@ -7,7 +7,6 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/run_loop.h" #include "base/test/histogram_tester.h" #include "base/test/simple_test_tick_clock.h" #include "base/test/test_simple_task_runner.h" diff --git a/components/proximity_auth/unlock_manager.cc b/components/proximity_auth/unlock_manager.cc new file mode 100644 index 0000000..d9156fc --- /dev/null +++ b/components/proximity_auth/unlock_manager.cc @@ -0,0 +1,467 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/proximity_auth/unlock_manager.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/proximity_auth/client.h" +#include "components/proximity_auth/logging/logging.h" +#include "components/proximity_auth/metrics.h" +#include "components/proximity_auth/proximity_auth_client.h" +#include "components/proximity_auth/proximity_monitor.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" + +#if defined(OS_CHROMEOS) +#include "chromeos/dbus/dbus_thread_manager.h" + +using chromeos::DBusThreadManager; +#endif // defined(OS_CHROMEOS) + +namespace proximity_auth { +namespace { + +// The maximum amount of time, in seconds, that the unlock manager can stay in +// the 'waking up' state after resuming from sleep. +const int kWakingUpDurationSecs = 5; + +// The limit, in seconds, on the elapsed time for an auth attempt. If an auth +// attempt exceeds this limit, it will time out and be rejected. This is +// provided as a failsafe, in case something goes wrong. +const int kAuthAttemptTimeoutSecs = 5; + +// Returns the remote device's security settings state, for metrics, +// corresponding to a remote status update. +metrics::RemoteSecuritySettingsState GetRemoteSecuritySettingsState( + const RemoteStatusUpdate& status_update) { + switch (status_update.secure_screen_lock_state) { + case SECURE_SCREEN_LOCK_STATE_UNKNOWN: + return metrics::RemoteSecuritySettingsState::UNKNOWN; + + case SECURE_SCREEN_LOCK_DISABLED: + switch (status_update.trust_agent_state) { + case TRUST_AGENT_UNSUPPORTED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_DISABLED_TRUST_AGENT_UNSUPPORTED; + case TRUST_AGENT_DISABLED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_DISABLED_TRUST_AGENT_DISABLED; + case TRUST_AGENT_ENABLED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_DISABLED_TRUST_AGENT_ENABLED; + } + + case SECURE_SCREEN_LOCK_ENABLED: + switch (status_update.trust_agent_state) { + case TRUST_AGENT_UNSUPPORTED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_ENABLED_TRUST_AGENT_UNSUPPORTED; + case TRUST_AGENT_DISABLED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_ENABLED_TRUST_AGENT_DISABLED; + case TRUST_AGENT_ENABLED: + return metrics::RemoteSecuritySettingsState:: + SCREEN_LOCK_ENABLED_TRUST_AGENT_ENABLED; + } + } + + NOTREACHED(); + return metrics::RemoteSecuritySettingsState::UNKNOWN; +} + +} // namespace + +UnlockManager::UnlockManager(ScreenlockType screenlock_type, + scoped_ptr<ProximityMonitor> proximity_monitor, + ProximityAuthClient* proximity_auth_client) + : screenlock_type_(screenlock_type), + controller_(nullptr), + client_(nullptr), + proximity_monitor_(proximity_monitor.Pass()), + proximity_auth_client_(proximity_auth_client), + is_locked_(false), + is_attempting_auth_(false), + is_waking_up_(false), + screenlock_state_(ScreenlockState::INACTIVE), + clear_waking_up_state_weak_ptr_factory_(this), + reject_auth_attempt_weak_ptr_factory_(this), + weak_ptr_factory_(this) { + // TODO(isherman): Register for auth attempt notifications, equivalent to the + // JavaScript lines: + // + // chrome.screenlockPrivate.onAuthAttempted.addListener( + // this.onAuthAttempted_.bind(this)); + + ScreenlockBridge* screenlock_bridge = ScreenlockBridge::Get(); + screenlock_bridge->AddObserver(this); + OnScreenLockedOrUnlocked(screenlock_bridge->IsLocked()); + +#if defined(OS_CHROMEOS) + DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); +#endif // defined(OS_CHROMEOS) + SetWakingUpState(true); + + if (device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&UnlockManager::OnBluetoothAdapterInitialized, + weak_ptr_factory_.GetWeakPtr())); + } +} + +UnlockManager::~UnlockManager() { + if (client_) + client_->RemoveObserver(this); + + ScreenlockBridge::Get()->RemoveObserver(this); + +#if defined(OS_CHROMEOS) + DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); +#endif // defined(OS_CHROMEOS) + + if (bluetooth_adapter_) + bluetooth_adapter_->RemoveObserver(this); +} + +bool UnlockManager::IsUnlockAllowed() { + return (remote_screenlock_state_ && + *remote_screenlock_state_ == RemoteScreenlockState::UNLOCKED && + controller_ && + controller_->GetState() == + Controller::State::SECURE_CHANNEL_ESTABLISHED && + proximity_monitor_->IsUnlockAllowed() && + (screenlock_type_ != ScreenlockType::SIGN_IN || + (client_ && client_->SupportsSignIn()))); +} + +void UnlockManager::SetController(Controller* controller) { + if (client_) { + client_->RemoveObserver(this); + client_ = nullptr; + } + + controller_ = controller; + if (controller_) + SetWakingUpState(true); + + UpdateLockScreen(); +} + +void UnlockManager::OnControllerStateChanged() { + Controller::State state = controller_->GetState(); + PA_LOG(INFO) << "[Unlock] Controller state changed: " + << static_cast<int>(state); + + remote_screenlock_state_.reset(); + if (state == Controller::State::SECURE_CHANNEL_ESTABLISHED) { + client_ = controller_->GetClient(); + client_->AddObserver(this); + } + + if (state == Controller::State::AUTHENTICATION_FAILED) + SetWakingUpState(false); + + UpdateLockScreen(); +} + +void UnlockManager::OnUnlockEventSent(bool success) { + if (!is_attempting_auth_) { + PA_LOG(ERROR) << "[Unlock] Sent easy_unlock event, but no auth attempted."; + return; + } + + if (sign_in_secret_ && success) + proximity_auth_client_->FinalizeSignin(*sign_in_secret_); + + AcceptAuthAttempt(success); +} + +void UnlockManager::OnRemoteStatusUpdate( + const RemoteStatusUpdate& status_update) { + PA_LOG(INFO) << "[Unlock] Status Update: (" + << "user_present=" << status_update.user_presence << ", " + << "secure_screen_lock=" + << status_update.secure_screen_lock_state << ", " + << "trust_agent=" << status_update.trust_agent_state << ")"; + metrics::RecordRemoteSecuritySettingsState( + GetRemoteSecuritySettingsState(status_update)); + + remote_screenlock_state_.reset(new RemoteScreenlockState( + GetScreenlockStateFromRemoteUpdate(status_update))); + + // This also calls |UpdateLockScreen()| + SetWakingUpState(false); +} + +void UnlockManager::OnDecryptResponse(scoped_ptr<std::string> decrypted_bytes) { + if (!is_attempting_auth_) { + PA_LOG(ERROR) << "[Unlock] Decrypt response received but not attempting " + << "auth."; + return; + } + + if (!decrypted_bytes) { + PA_LOG(INFO) << "[Unlock] Failed to decrypt sign-in challenge."; + AcceptAuthAttempt(false); + } else { + sign_in_secret_ = decrypted_bytes.Pass(); + client_->DispatchUnlockEvent(); + } +} + +void UnlockManager::OnUnlockResponse(bool success) { + if (!is_attempting_auth_) { + PA_LOG(ERROR) << "[Unlock] Unlock response received but not attempting " + << "auth."; + return; + } + + PA_LOG(INFO) << "[Unlock] Unlock response from remote device: " + << (success ? "success" : "failure"); + if (success) + client_->DispatchUnlockEvent(); + else + AcceptAuthAttempt(false); +} + +void UnlockManager::OnDisconnected() { + client_->RemoveObserver(this); + client_ = nullptr; +} + +void UnlockManager::OnScreenDidLock( + ScreenlockBridge::LockHandler::ScreenType screen_type) { + OnScreenLockedOrUnlocked(true); +} + +void UnlockManager::OnScreenDidUnlock( + ScreenlockBridge::LockHandler::ScreenType screen_type) { + OnScreenLockedOrUnlocked(false); +} + +void UnlockManager::OnFocusedUserChanged(const std::string& user_id) {} + +void UnlockManager::OnScreenLockedOrUnlocked(bool is_locked) { + // TODO(tengs): Chrome will only start connecting to the phone when + // the screen is locked, for privacy reasons. We should reinvestigate + // this behaviour if we want automatic locking. + if (is_locked && bluetooth_adapter_ && bluetooth_adapter_->IsPowered() && + controller_ && + controller_->GetState() == Controller::State::FINDING_CONNECTION) { + SetWakingUpState(true); + } + + is_locked_ = is_locked; + UpdateProximityMonitorState(); +} + +void UnlockManager::OnBluetoothAdapterInitialized( + scoped_refptr<device::BluetoothAdapter> adapter) { + bluetooth_adapter_ = adapter; + bluetooth_adapter_->AddObserver(this); +} + +void UnlockManager::AdapterPresentChanged(device::BluetoothAdapter* adapter, + bool present) { + UpdateLockScreen(); +} + +void UnlockManager::AdapterPoweredChanged(device::BluetoothAdapter* adapter, + bool powered) { + UpdateLockScreen(); +} + +#if defined(OS_CHROMEOS) +void UnlockManager::SuspendDone(const base::TimeDelta& sleep_duration) { + SetWakingUpState(true); +} +#endif // defined(OS_CHROMEOS) + +void UnlockManager::OnAuthAttempted( + ScreenlockBridge::LockHandler::AuthType auth_type) { + if (is_attempting_auth_) { + PA_LOG(INFO) << "[Unlock] Already attempting auth."; + return; + } + + if (auth_type != ScreenlockBridge::LockHandler::USER_CLICK) + return; + + is_attempting_auth_ = true; + + if (!controller_) { + PA_LOG(ERROR) << "[Unlock] No controller active when auth is attempted"; + AcceptAuthAttempt(false); + UpdateLockScreen(); + return; + } + + if (!IsUnlockAllowed()) { + AcceptAuthAttempt(false); + UpdateLockScreen(); + return; + } + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&UnlockManager::AcceptAuthAttempt, + reject_auth_attempt_weak_ptr_factory_.GetWeakPtr(), false), + base::TimeDelta::FromSeconds(kAuthAttemptTimeoutSecs)); + + if (screenlock_type_ == ScreenlockType::SIGN_IN) { + SendSignInChallenge(); + } else { + if (client_->SupportsSignIn()) { + client_->RequestUnlock(); + } else { + PA_LOG(INFO) << "[Unlock] Protocol v3.1 not supported, skipping " + << "request_unlock."; + client_->DispatchUnlockEvent(); + } + } +} + +void UnlockManager::SendSignInChallenge() { + // TODO(isherman): Implement. + NOTIMPLEMENTED(); +} + +ScreenlockState UnlockManager::GetScreenlockState() { + if (!controller_ || controller_->GetState() == Controller::State::STOPPED) + return ScreenlockState::INACTIVE; + + if (IsUnlockAllowed()) + return ScreenlockState::AUTHENTICATED; + + if (controller_->GetState() == Controller::State::AUTHENTICATION_FAILED) + return ScreenlockState::PHONE_NOT_AUTHENTICATED; + + if (is_waking_up_) + return ScreenlockState::BLUETOOTH_CONNECTING; + + if (!bluetooth_adapter_ || !bluetooth_adapter_->IsPowered()) + return ScreenlockState::NO_BLUETOOTH; + + if (screenlock_type_ == ScreenlockType::SIGN_IN && client_ && + !client_->SupportsSignIn()) + return ScreenlockState::PHONE_UNSUPPORTED; + + // If the RSSI is too low, then the remote device is nowhere near the local + // device. This message should take priority over messages about screen lock + // states. + if (!proximity_monitor_->IsUnlockAllowed() && + !proximity_monitor_->IsInRssiRange()) + return ScreenlockState::RSSI_TOO_LOW; + + if (remote_screenlock_state_) { + switch (*remote_screenlock_state_) { + case RemoteScreenlockState::DISABLED: + return ScreenlockState::PHONE_NOT_LOCKABLE; + + case RemoteScreenlockState::LOCKED: + if (proximity_monitor_->GetStrategy() == + ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER && + !proximity_monitor_->IsUnlockAllowed()) { + return ScreenlockState::PHONE_LOCKED_AND_TX_POWER_TOO_HIGH; + } + return ScreenlockState::PHONE_LOCKED; + + case RemoteScreenlockState::UNKNOWN: + return ScreenlockState::PHONE_UNSUPPORTED; + + case RemoteScreenlockState::UNLOCKED: + // Handled by the code below. + break; + } + } + + if (!proximity_monitor_->IsUnlockAllowed()) { + ProximityMonitor::Strategy strategy = proximity_monitor_->GetStrategy(); + if (strategy != ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER) { + // CHECK_RSSI should have been handled above, and no other states should + // prevent unlocking. + PA_LOG(ERROR) << "[Unlock] Invalid ProximityMonitor strategy: " + << static_cast<int>(strategy); + return ScreenlockState::NO_PHONE; + } + return ScreenlockState::TX_POWER_TOO_HIGH; + } + + return ScreenlockState::NO_PHONE; +} + +void UnlockManager::UpdateLockScreen() { + UpdateProximityMonitorState(); + + ScreenlockState new_state = GetScreenlockState(); + if (screenlock_state_ == new_state) + return; + + proximity_auth_client_->UpdateScreenlockState(new_state); + screenlock_state_ = new_state; +} + +void UnlockManager::UpdateProximityMonitorState() { + if (is_locked_ && controller_ && + controller_->GetState() == + Controller::State::SECURE_CHANNEL_ESTABLISHED) { + proximity_monitor_->Start(); + } else { + proximity_monitor_->Stop(); + } +} + +void UnlockManager::SetWakingUpState(bool is_waking_up) { + is_waking_up_ = is_waking_up; + + // Clear the waking up state after a timeout. + clear_waking_up_state_weak_ptr_factory_.InvalidateWeakPtrs(); + if (is_waking_up_) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&UnlockManager::SetWakingUpState, + clear_waking_up_state_weak_ptr_factory_.GetWeakPtr(), false), + base::TimeDelta::FromSeconds(kWakingUpDurationSecs)); + } + + UpdateLockScreen(); +} + +void UnlockManager::AcceptAuthAttempt(bool should_accept) { + if (!is_attempting_auth_) + return; + + // Cancel the pending task to time out the auth attempt. + reject_auth_attempt_weak_ptr_factory_.InvalidateWeakPtrs(); + + if (should_accept) + proximity_monitor_->RecordProximityMetricsOnAuthSuccess(); + + is_attempting_auth_ = false; + proximity_auth_client_->FinalizeUnlock(should_accept); +} + +UnlockManager::RemoteScreenlockState +UnlockManager::GetScreenlockStateFromRemoteUpdate(RemoteStatusUpdate update) { + switch (update.secure_screen_lock_state) { + case SECURE_SCREEN_LOCK_DISABLED: + return RemoteScreenlockState::DISABLED; + + case SECURE_SCREEN_LOCK_ENABLED: + if (update.user_presence == USER_PRESENT) + return RemoteScreenlockState::UNLOCKED; + + return RemoteScreenlockState::LOCKED; + + case SECURE_SCREEN_LOCK_STATE_UNKNOWN: + return RemoteScreenlockState::UNKNOWN; + } + + NOTREACHED(); + return RemoteScreenlockState::UNKNOWN; +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/unlock_manager.h b/components/proximity_auth/unlock_manager.h new file mode 100644 index 0000000..9f40541 --- /dev/null +++ b/components/proximity_auth/unlock_manager.h @@ -0,0 +1,197 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H +#define COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "components/proximity_auth/client_observer.h" +#include "components/proximity_auth/controller.h" +#include "components/proximity_auth/remote_status_update.h" +#include "components/proximity_auth/screenlock_bridge.h" +#include "components/proximity_auth/screenlock_state.h" +#include "device/bluetooth/bluetooth_adapter.h" + +#if defined(OS_CHROMEOS) +#include "chromeos/dbus/power_manager_client.h" +#endif + +namespace proximity_auth { + +class Client; +class ProximityAuthClient; +class ProximityMonitor; + +// The unlock manager is responsible for controlling the lock screen UI based on +// the authentication status of the registered remote devices. +class UnlockManager : public ClientObserver, + public ScreenlockBridge::Observer, +#if defined(OS_CHROMEOS) + chromeos::PowerManagerClient::Observer, +#endif // defined(OS_CHROMEOS) + public device::BluetoothAdapter::Observer { + public: + enum class ScreenlockType { + SESSION_LOCK, + SIGN_IN, + }; + + // The |proximity_auth_client| is not owned and should outlive the constructed + // unlock manager. + // TODO(isherman): Rather than passing a single ProximityMonitor instance, we + // should pass a factory, as the UnlockManager should create and destroy + // ProximityMonitors as needed. Currently, the expectations are misaligned + // between the ProximityMonitor and the UnlockManager classes. + UnlockManager(ScreenlockType screenlock_type, + scoped_ptr<ProximityMonitor> proximity_monitor, + ProximityAuthClient* proximity_auth_client); + ~UnlockManager() override; + + // Whether proximity-based unlocking is currently allowed. True if any one of + // the remote devices is authenticated and in range. + bool IsUnlockAllowed(); + + // Sets the |controller| to which local events are dispatched. A null + // controller indicates that proximity-based authentication is inactive. + void SetController(Controller* controller); + + // Called when the controller's state changes. + void OnControllerStateChanged(); + + protected: + // Called when the user pod is clicked for an authentication attempt of type + // |auth_type|. + // Exposed for testing. + void OnAuthAttempted(ScreenlockBridge::LockHandler::AuthType auth_type); + + private: + // The possible lock screen states for the remote device. + enum class RemoteScreenlockState { + UNKNOWN, + UNLOCKED, + DISABLED, + LOCKED, + }; + + // ClientObserver: + void OnUnlockEventSent(bool success) override; + void OnRemoteStatusUpdate(const RemoteStatusUpdate& status_update) override; + void OnDecryptResponse(scoped_ptr<std::string> decrypted_bytes) override; + void OnUnlockResponse(bool success) override; + void OnDisconnected() override; + + // ScreenlockBridge::Observer + void OnScreenDidLock( + ScreenlockBridge::LockHandler::ScreenType screen_type) override; + void OnScreenDidUnlock( + ScreenlockBridge::LockHandler::ScreenType screen_type) override; + void OnFocusedUserChanged(const std::string& user_id) override; + + // Called when the screenlock state changes. + void OnScreenLockedOrUnlocked(bool is_locked); + + // Called when the Bluetooth adapter is initialized. + void OnBluetoothAdapterInitialized( + scoped_refptr<device::BluetoothAdapter> adapter); + + // device::BluetoothAdapter::Observer: + void AdapterPresentChanged(device::BluetoothAdapter* adapter, + bool present) override; + void AdapterPoweredChanged(device::BluetoothAdapter* adapter, + bool powered) override; + +#if defined(OS_CHROMEOS) + // chromeos::PowerManagerClient::Observer: + void SuspendDone(const base::TimeDelta& sleep_duration) override; +#endif // defined(OS_CHROMEOS) + + // Called when auth is attempted to send the sign-in challenge to the remote + // device for decryption. + void SendSignInChallenge(); + + // Returns the current state for the screen lock UI. + ScreenlockState GetScreenlockState(); + + // Updates the lock screen based on the manager's current state. + void UpdateLockScreen(); + + // Activates or deactivates the proximity monitor, as appropriate given the + // current state of |this| unlock manager. + void UpdateProximityMonitorState(); + + // Sets waking up state. + void SetWakingUpState(bool is_waking_up); + + // Accepts or rejects the current auth attempt according to |should_accept|. + // If the auth attempt is accepted, unlocks the screen. + void AcceptAuthAttempt(bool should_accept); + + // Returns the screen lock state corresponding to the given remote |status| + // update. + RemoteScreenlockState GetScreenlockStateFromRemoteUpdate( + RemoteStatusUpdate update); + + // Whether |this| manager is being used for sign-in or session unlock. + const ScreenlockType screenlock_type_; + + // Whether the user is present at the remote device. Unset if no remote status + // update has yet been received. + scoped_ptr<RemoteScreenlockState> remote_screenlock_state_; + + // Controls the proximity auth flow logic. Not owned, and expcted to outlive + // |this| instance. + Controller* controller_; + + // The client used to communicate with the remote device once a secure channel + // is established. Null if no secure channel has been established yet. Not + // owned, and expected to outlive |this| instance. + Client* client_; + + // Tracks whether the remote device is currently in close enough proximity to + // the local device to allow unlocking. + scoped_ptr<ProximityMonitor> proximity_monitor_; + + // Used to call into the embedder. Expected to outlive |this| instance. + ProximityAuthClient* proximity_auth_client_; + + // Whether the screen is currently locked. + bool is_locked_; + + // True if the manager is currently processing a user-initiated authentication + // attempt, which is initiated when the user pod is clicked. + bool is_attempting_auth_; + + // Whether the system is waking up from sleep. + bool is_waking_up_; + + // The Bluetooth adapter. Null if there is no adapter present on the local + // device. + scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_; + + // The sign-in secret received from the remote device by decrypting the + // sign-in challenge. + scoped_ptr<std::string> sign_in_secret_; + + // The state of the current screen lock UI. + ScreenlockState screenlock_state_; + + // Used to clear the waking up state after a timeout. + base::WeakPtrFactory<UnlockManager> clear_waking_up_state_weak_ptr_factory_; + + // Used to reject auth attempts after a timeout. An in-progress auth attempt + // blocks the sign-in screen UI, so it's important to prevent the auth attempt + // from blocking the UI in case a step in the code path hangs. + base::WeakPtrFactory<UnlockManager> reject_auth_attempt_weak_ptr_factory_; + + // Used to vend all other weak pointers. + base::WeakPtrFactory<UnlockManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(UnlockManager); +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H diff --git a/components/proximity_auth/unlock_manager_unittest.cc b/components/proximity_auth/unlock_manager_unittest.cc new file mode 100644 index 0000000..effc52f --- /dev/null +++ b/components/proximity_auth/unlock_manager_unittest.cc @@ -0,0 +1,814 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/proximity_auth/unlock_manager.h" + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/test_simple_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/proximity_auth/client.h" +#include "components/proximity_auth/controller.h" +#include "components/proximity_auth/logging/logging.h" +#include "components/proximity_auth/proximity_auth_client.h" +#include "components/proximity_auth/proximity_monitor.h" +#include "components/proximity_auth/remote_status_update.h" +#include "components/proximity_auth/screenlock_bridge.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_CHROMEOS) +#include "chromeos/dbus/dbus_thread_manager.h" +#endif // defined(OS_CHROMEOS) + +using testing::AtLeast; +using testing::NiceMock; +using testing::Return; +using testing::_; + +namespace proximity_auth { +namespace { + +// Note that the trust agent state is currently ignored by the UnlockManager +// implementation. +RemoteStatusUpdate kRemoteScreenUnlocked = { + USER_PRESENT, SECURE_SCREEN_LOCK_ENABLED, TRUST_AGENT_UNSUPPORTED}; +RemoteStatusUpdate kRemoteScreenLocked = { + USER_ABSENT, SECURE_SCREEN_LOCK_ENABLED, TRUST_AGENT_UNSUPPORTED}; +RemoteStatusUpdate kRemoteScreenlockDisabled = { + USER_PRESENT, SECURE_SCREEN_LOCK_DISABLED, TRUST_AGENT_UNSUPPORTED}; +RemoteStatusUpdate kRemoteScreenlockStateUnknown = { + USER_PRESENCE_UNKNOWN, SECURE_SCREEN_LOCK_STATE_UNKNOWN, + TRUST_AGENT_UNSUPPORTED}; + +class MockController : public Controller { + public: + MockController() {} + ~MockController() override {} + + MOCK_CONST_METHOD0(GetState, State()); + MOCK_METHOD0(GetClient, Client*()); +}; + +class MockClient : public Client { + public: + MockClient() {} + ~MockClient() override {} + + MOCK_METHOD1(AddObserver, void(ClientObserver* observer)); + MOCK_METHOD1(RemoveObserver, void(ClientObserver* observer)); + MOCK_CONST_METHOD0(SupportsSignIn, bool()); + MOCK_METHOD0(DispatchUnlockEvent, void()); + MOCK_METHOD1(RequestDecryption, void(const std::string& challenge)); + MOCK_METHOD0(RequestUnlock, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockClient); +}; + +class MockProximityMonitor : public ProximityMonitor { + public: + MockProximityMonitor() { + ON_CALL(*this, GetStrategy()) + .WillByDefault(Return(ProximityMonitor::Strategy::NONE)); + ON_CALL(*this, IsUnlockAllowed()).WillByDefault(Return(true)); + ON_CALL(*this, IsInRssiRange()).WillByDefault(Return(false)); + } + ~MockProximityMonitor() override {} + + MOCK_METHOD0(Start, void()); + MOCK_METHOD0(Stop, void()); + MOCK_CONST_METHOD0(GetStrategy, Strategy()); + MOCK_CONST_METHOD0(IsUnlockAllowed, bool()); + MOCK_CONST_METHOD0(IsInRssiRange, bool()); + MOCK_METHOD0(RecordProximityMetricsOnAuthSuccess, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProximityMonitor); +}; + +class MockProximityAuthClient : public ProximityAuthClient { + public: + MockProximityAuthClient() {} + ~MockProximityAuthClient() override {} + + MOCK_CONST_METHOD0(GetAuthenticatedUsername, std::string()); + MOCK_METHOD1(UpdateScreenlockState, + void(proximity_auth::ScreenlockState state)); + MOCK_METHOD1(FinalizeUnlock, void(bool success)); + MOCK_METHOD1(FinalizeSignin, void(const std::string& secret)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProximityAuthClient); +}; + +class FakeLockHandler : public ScreenlockBridge::LockHandler { + public: + FakeLockHandler() {} + ~FakeLockHandler() override {} + + // LockHandler: + void ShowBannerMessage(const base::string16& message) override {} + void ShowUserPodCustomIcon( + const std::string& user_email, + const ScreenlockBridge::UserPodCustomIconOptions& icon) override {} + void HideUserPodCustomIcon(const std::string& user_email) override {} + void EnableInput() override {} + void SetAuthType(const std::string& user_email, + AuthType auth_type, + const base::string16& auth_value) override {} + AuthType GetAuthType(const std::string& user_email) const override { + return USER_CLICK; + } + ScreenType GetScreenType() const override { return LOCK_SCREEN; } + void Unlock(const std::string& user_email) override {} + void AttemptEasySignin(const std::string& user_email, + const std::string& secret, + const std::string& key_label) override {} + + private: + DISALLOW_COPY_AND_ASSIGN(FakeLockHandler); +}; + +class TestUnlockManager : public UnlockManager { + public: + TestUnlockManager(ScreenlockType screenlock_type, + scoped_ptr<ProximityMonitor> proximity_monitor, + ProximityAuthClient* proximity_auth_client) + : UnlockManager(screenlock_type, + proximity_monitor.Pass(), + proximity_auth_client) {} + ~TestUnlockManager() override {} + + using UnlockManager::OnAuthAttempted; + using ClientObserver::OnUnlockEventSent; + using ClientObserver::OnRemoteStatusUpdate; + using ClientObserver::OnDecryptResponse; + using ClientObserver::OnUnlockResponse; + using ClientObserver::OnDisconnected; + using ScreenlockBridge::Observer::OnScreenDidLock; + using ScreenlockBridge::Observer::OnScreenDidUnlock; + using ScreenlockBridge::Observer::OnFocusedUserChanged; +}; + +// Creates a mock Bluetooth adapter and sets it as the global adapter for +// testing. +scoped_refptr<device::MockBluetoothAdapter> +CreateAndRegisterMockBluetoothAdapter() { + scoped_refptr<device::MockBluetoothAdapter> adapter = + new NiceMock<device::MockBluetoothAdapter>(); + device::BluetoothAdapterFactory::SetAdapterForTesting(adapter); + return adapter; +} + +} // namespace + +class ProximityAuthUnlockManagerTest : public testing::Test { + public: + ProximityAuthUnlockManagerTest() + : bluetooth_adapter_(CreateAndRegisterMockBluetoothAdapter()), + proximity_monitor_(nullptr), + task_runner_(new base::TestSimpleTaskRunner()), + thread_task_runner_handle_(task_runner_) { + ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(true)); + ON_CALL(controller_, GetClient()).WillByDefault(Return(&client_)); + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true)); + + ScreenlockBridge::Get()->SetLockHandler(&lock_handler_); + +#if defined(OS_CHROMEOS) + chromeos::DBusThreadManager::Initialize(); +#endif + } + + ~ProximityAuthUnlockManagerTest() override { + // Make sure to verify the mock prior to the destruction of the unlock + // manager, as otherwise it's impossible to tell whether calls to Stop() + // occur as a side-effect of the destruction or from the code intended to be + // under test. + if (proximity_monitor_) + testing::Mock::VerifyAndClearExpectations(proximity_monitor_); + + // The UnlockManager must be destroyed before calling + // chromeos::DBusThreadManager::Shutdown(), as the UnlockManager's + // destructor references the DBusThreadManager. + unlock_manager_.reset(); + +#if defined(OS_CHROMEOS) + chromeos::DBusThreadManager::Shutdown(); +#endif + + ScreenlockBridge::Get()->SetLockHandler(nullptr); + } + + void CreateUnlockManager(UnlockManager::ScreenlockType screenlock_type) { + proximity_monitor_ = new NiceMock<MockProximityMonitor>; + unlock_manager_.reset(new TestUnlockManager( + screenlock_type, make_scoped_ptr(proximity_monitor_), + &proximity_auth_client_)); + } + + void SimulateUserPresentState() { + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->OnControllerStateChanged(); + + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + } + + void RunPendingTasks() { task_runner_->RunPendingTasks(); } + + protected: + // Mock used for verifying interactions with the Bluetooth subsystem. + scoped_refptr<device::MockBluetoothAdapter> bluetooth_adapter_; + + NiceMock<MockProximityAuthClient> proximity_auth_client_; + NiceMock<MockController> controller_; + NiceMock<MockClient> client_; + scoped_ptr<TestUnlockManager> unlock_manager_; + // Owned by the |unlock_manager_|. + MockProximityMonitor* proximity_monitor_; + + private: + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; + base::ThreadTaskRunnerHandle thread_task_runner_handle_; + FakeLockHandler lock_handler_; + ScopedDisableLoggingForTesting disable_logging_; +}; + +TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_InitialState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SessionLock_AllGood) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_TRUE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SignIn_AllGood) { + CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->OnControllerStateChanged(); + + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true)); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_TRUE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_SignIn_ClientDoesNotSupportSignIn) { + CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->OnControllerStateChanged(); + + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false)); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SignIn_ClientIsNull) { + CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + ON_CALL(controller_, GetClient()).WillByDefault(Return(nullptr)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_DisallowedByProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + ON_CALL(*proximity_monitor_, IsUnlockAllowed()).WillByDefault(Return(false)); + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_SecureChannelNotEstablished) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATING)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_ControllerIsNull) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + unlock_manager_->SetController(nullptr); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_RemoteScreenlockStateLocked) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenLocked); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_RemoteScreenlockStateUnknown) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockStateUnknown); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_RemoteScreenlockStateDisabled) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockDisabled); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, + IsUnlockAllowed_RemoteScreenlockStateNotYetReceived) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->SetController(&controller_); + + EXPECT_FALSE(unlock_manager_->IsUnlockAllowed()); +} + +TEST_F(ProximityAuthUnlockManagerTest, SetController_SetToNull) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::INACTIVE)); + unlock_manager_->SetController(nullptr); +} + +TEST_F(ProximityAuthUnlockManagerTest, SetController_ExistingController) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, UpdateScreenlockState(_)).Times(0); + unlock_manager_->SetController(&controller_); +} + +TEST_F(ProximityAuthUnlockManagerTest, + SetController_NullThenExistingController) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::INACTIVE)); + unlock_manager_->SetController(nullptr); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::AUTHENTICATED)); + unlock_manager_->SetController(&controller_); +} + +TEST_F(ProximityAuthUnlockManagerTest, SetController_AuthenticationFailed) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_->SetController(nullptr); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED)); + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED)); + unlock_manager_->SetController(&controller_); +} + +TEST_F(ProximityAuthUnlockManagerTest, SetController_WakingUp) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_->SetController(nullptr); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::FINDING_CONNECTION)); + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING)); + unlock_manager_->SetController(&controller_); +} + +TEST_F(ProximityAuthUnlockManagerTest, + SetController_NullController_StopsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1)); + unlock_manager_->SetController(nullptr); +} + +TEST_F(ProximityAuthUnlockManagerTest, + SetController_ConnectingController_StopsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + NiceMock<MockController> controller; + ON_CALL(controller, GetState()) + .WillByDefault(Return(Controller::State::FINDING_CONNECTION)); + + EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1)); + unlock_manager_->SetController(&controller); +} + +TEST_F(ProximityAuthUnlockManagerTest, + SetController_ConnectedController_StartsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + NiceMock<MockController> controller; + ON_CALL(controller, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + + EXPECT_CALL(*proximity_monitor_, Start()).Times(AtLeast(1)); + unlock_manager_->SetController(&controller); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_SecureChannelEstablished_RegistersAsObserver) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + NiceMock<MockController> controller; + ON_CALL(controller, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + + EXPECT_CALL(client_, AddObserver(unlock_manager_.get())); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_StartsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + NiceMock<MockController> controller; + ON_CALL(controller, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + + EXPECT_CALL(*proximity_monitor_, Start()).Times(AtLeast(1)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_StopsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED)); + + EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_Stopped_UpdatesScreenlockState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::INACTIVE)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_AuthenticationFailed_UpdatesScreenlockState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED)); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_FindingConnection_UpdatesScreenlockState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::FINDING_CONNECTION)); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnControllerStateChanged_Authenticating_UpdatesScreenlockState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATING)); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F( + ProximityAuthUnlockManagerTest, + OnControllerStateChanged_SecureChannelEstablished_UpdatesScreenlockState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING)); + unlock_manager_->OnControllerStateChanged(); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnDisconnected_UnregistersAsObserver) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED)); + + EXPECT_CALL(client_, RemoveObserver(unlock_manager_.get())); + unlock_manager_.get()->OnDisconnected(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnScreenDidUnlock_StopsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(*proximity_monitor_, Stop()); + unlock_manager_.get()->OnScreenDidUnlock( + ScreenlockBridge::LockHandler::LOCK_SCREEN); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnScreenDidLock_StartsProximityMonitor) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::STOPPED)); + unlock_manager_->SetController(&controller_); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED)); + unlock_manager_->OnControllerStateChanged(); + + EXPECT_CALL(*proximity_monitor_, Start()); + unlock_manager_.get()->OnScreenDidLock( + ScreenlockBridge::LockHandler::LOCK_SCREEN); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnScreenDidLock_SetsWakingUpState) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_.get()->OnScreenDidUnlock( + ScreenlockBridge::LockHandler::LOCK_SCREEN); + + ON_CALL(controller_, GetState()) + .WillByDefault(Return(Controller::State::FINDING_CONNECTION)); + unlock_manager_->OnControllerStateChanged(); + + EXPECT_CALL(proximity_auth_client_, + UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING)); + unlock_manager_.get()->OnScreenDidLock( + ScreenlockBridge::LockHandler::LOCK_SCREEN); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnDecryptResponse_NoAuthAttemptInProgress) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0); + unlock_manager_.get()->OnDecryptResponse(nullptr); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnUnlockEventSent_NoAuthAttemptInProgress) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0); + unlock_manager_.get()->OnUnlockEventSent(true); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnUnlockResponse_NoAuthAttemptInProgress) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0); + unlock_manager_.get()->OnUnlockResponse(true); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_NoController) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_->SetController(nullptr); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_UnlockNotAllowed) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + ON_CALL(*proximity_monitor_, IsUnlockAllowed()).WillByDefault(Return(false)); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_NotUserClick) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0); + unlock_manager_->OnAuthAttempted( + ScreenlockBridge::LockHandler::EXPAND_THEN_USER_CLICK); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_DuplicateCall) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, RequestUnlock()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(client_, RequestUnlock()).Times(0); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); +} + +TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_TimesOut) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + // Simulate the timeout period elapsing. + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + RunPendingTasks(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_DoesntTimeOutFollowingResponse) { + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)); + unlock_manager_->OnUnlockResponse(false); + + // Simulate the timeout period elapsing. + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0); + RunPendingTasks(); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_Unlock_SupportsSignIn_UnlockRequestFails) { + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true)); + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, RequestUnlock()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + unlock_manager_->OnUnlockResponse(false); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_Unlock_WithSignIn_RequestSucceeds_EventSendFails) { + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true)); + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, RequestUnlock()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(client_, DispatchUnlockEvent()); + unlock_manager_->OnUnlockResponse(true); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + unlock_manager_->OnUnlockEventSent(false); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_Unlock_WithSignIn_RequestSucceeds_EventSendSucceeds) { + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true)); + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, RequestUnlock()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(client_, DispatchUnlockEvent()); + unlock_manager_->OnUnlockResponse(true); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(true)); + unlock_manager_->OnUnlockEventSent(true); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_Unlock_DoesntSupportSignIn_UnlockEventSendFails) { + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false)); + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, DispatchUnlockEvent()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false)); + unlock_manager_->OnUnlockEventSent(false); +} + +TEST_F(ProximityAuthUnlockManagerTest, + OnAuthAttempted_Unlock_SupportsSignIn_UnlockEventSendSucceeds) { + ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false)); + CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK); + SimulateUserPresentState(); + + EXPECT_CALL(client_, DispatchUnlockEvent()); + unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK); + + EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(true)); + unlock_manager_->OnUnlockEventSent(true); +} + +} // namespace proximity_auth |