Chromium Code Reviews| Index: components/proximity_auth/unlock_manager.cc |
| diff --git a/components/proximity_auth/unlock_manager.cc b/components/proximity_auth/unlock_manager.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e778042e3ea4ef15692600459b4530fba8d7114d |
| --- /dev/null |
| +++ b/components/proximity_auth/unlock_manager.cc |
| @@ -0,0 +1,465 @@ |
| +// 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" |
| +#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); |
| + OnScreenLockStateChanged(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_); |
|
Tim Song
2015/07/23 21:32:15
Shouldn't you move these two lines inside AcceptAu
Ilya Sherman
2015/08/11 23:37:15
The current flow matches what the app is doing (se
|
| + |
| + 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) { |
| + OnScreenLockStateChanged(true); |
| +} |
| + |
| +void UnlockManager::OnScreenDidUnlock( |
| + ScreenlockBridge::LockHandler::ScreenType screen_type) { |
| + OnScreenLockStateChanged(false); |
| +} |
| + |
| +void UnlockManager::OnFocusedUserChanged(const std::string& user_id) {} |
| + |
| +void UnlockManager::OnScreenLockStateChanged(bool is_locked) { |
|
Tim Song
2015/07/23 21:32:15
There is already a |ScreenlockState| enum that has
Ilya Sherman
2015/08/11 23:37:15
Done.
|
| + // 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 |