// Copyright 2014 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 "chrome/browser/signin/easy_unlock_service.h" #include "base/bind.h" #include "base/command_line.h" #include "base/logging.h" #include "base/metrics/field_trial.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/prefs/scoped_user_pref_update.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/easy_unlock_auth_attempt.h" #include "chrome/browser/signin/easy_unlock_service_factory.h" #include "chrome/browser/signin/easy_unlock_service_observer.h" #include "chrome/browser/signin/screenlock_bridge.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/api/easy_unlock_private.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/pref_names.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/user_manager/user.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/common/one_shot_event.h" #include "grit/browser_resources.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h" #include "chrome/browser/chromeos/login/session/user_session_manager.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chromeos/chromeos_switches.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/power_manager_client.h" #endif namespace { extensions::ComponentLoader* GetComponentLoader( content::BrowserContext* context) { extensions::ExtensionSystem* extension_system = extensions::ExtensionSystem::Get(context); ExtensionService* extension_service = extension_system->extension_service(); return extension_service->component_loader(); } PrefService* GetLocalState() { return g_browser_process ? g_browser_process->local_state() : NULL; } } // namespace // static EasyUnlockService* EasyUnlockService::Get(Profile* profile) { return EasyUnlockServiceFactory::GetForProfile(profile); } // static EasyUnlockService* EasyUnlockService::GetForUser( const user_manager::User& user) { #if defined(OS_CHROMEOS) Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(&user); if (!profile) return NULL; return EasyUnlockService::Get(profile); #else return NULL; #endif } // static bool EasyUnlockService::IsSignInEnabled() { #if defined(OS_CHROMEOS) const std::string group_name = base::FieldTrialList::FindFullName("EasySignIn"); if (CommandLine::ForCurrentProcess()->HasSwitch( chromeos::switches::kDisableEasySignin)) { return false; } return group_name == "Enable"; #else return false; #endif } class EasyUnlockService::BluetoothDetector : public device::BluetoothAdapter::Observer { public: explicit BluetoothDetector(EasyUnlockService* service) : service_(service), weak_ptr_factory_(this) { } virtual ~BluetoothDetector() { if (adapter_.get()) adapter_->RemoveObserver(this); } void Initialize() { if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) return; device::BluetoothAdapterFactory::GetAdapter( base::Bind(&BluetoothDetector::OnAdapterInitialized, weak_ptr_factory_.GetWeakPtr())); } bool IsPresent() const { return adapter_.get() && adapter_->IsPresent(); } // device::BluetoothAdapter::Observer: virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter, bool present) OVERRIDE { service_->OnBluetoothAdapterPresentChanged(); } private: void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter) { adapter_ = adapter; adapter_->AddObserver(this); service_->OnBluetoothAdapterPresentChanged(); } // Owner of this class and should out-live this class. EasyUnlockService* service_; scoped_refptr<device::BluetoothAdapter> adapter_; base::WeakPtrFactory<BluetoothDetector> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(BluetoothDetector); }; #if defined(OS_CHROMEOS) class EasyUnlockService::PowerMonitor : public chromeos::PowerManagerClient::Observer { public: explicit PowerMonitor(EasyUnlockService* service) : service_(service), waking_up_(false), weak_ptr_factory_(this) { chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> AddObserver(this); } virtual ~PowerMonitor() { chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> RemoveObserver(this); } bool waking_up() const { return waking_up_; } private: // chromeos::PowerManagerClient::Observer: virtual void SuspendImminent() OVERRIDE { service_->PrepareForSuspend(); } virtual void SuspendDone(const base::TimeDelta& sleep_duration) OVERRIDE { waking_up_ = true; base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&PowerMonitor::ResetWakingUp, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromSeconds(5)); service_->UpdateAppState(); // Note that |this| may get deleted after |UpdateAppState| is called. } void ResetWakingUp() { waking_up_ = false; service_->UpdateAppState(); } EasyUnlockService* service_; bool waking_up_; base::WeakPtrFactory<PowerMonitor> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(PowerMonitor); }; #endif EasyUnlockService::EasyUnlockService(Profile* profile) : profile_(profile), bluetooth_detector_(new BluetoothDetector(this)), shut_down_(false), weak_ptr_factory_(this) { extensions::ExtensionSystem::Get(profile_)->ready().Post( FROM_HERE, base::Bind(&EasyUnlockService::Initialize, weak_ptr_factory_.GetWeakPtr())); } EasyUnlockService::~EasyUnlockService() { } // static void EasyUnlockService::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref( prefs::kEasyUnlockEnabled, false, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); registry->RegisterDictionaryPref( prefs::kEasyUnlockPairing, new base::DictionaryValue(), user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); registry->RegisterBooleanPref( prefs::kEasyUnlockAllowed, true, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); } // static void EasyUnlockService::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterDictionaryPref(prefs::kEasyUnlockHardlockState); } // static void EasyUnlockService::ResetLocalStateForUser(const std::string& user_id) { DCHECK(!user_id.empty()); PrefService* local_state = GetLocalState(); if (!local_state) return; DictionaryPrefUpdate update(local_state, prefs::kEasyUnlockHardlockState); update->RemoveWithoutPathExpansion(user_id, NULL); } bool EasyUnlockService::IsAllowed() { if (shut_down_) return false; if (!IsAllowedInternal()) return false; #if defined(OS_CHROMEOS) if (!bluetooth_detector_->IsPresent()) return false; return true; #else // TODO(xiyuan): Revisit when non-chromeos platforms are supported. return false; #endif } void EasyUnlockService::SetHardlockState( EasyUnlockScreenlockStateHandler::HardlockState state) { const std::string user_id = GetUserEmail(); if (user_id.empty()) return; SetHardlockStateForUser(user_id, state); } EasyUnlockScreenlockStateHandler::HardlockState EasyUnlockService::GetHardlockState() const { EasyUnlockScreenlockStateHandler::HardlockState state; if (GetPersistedHardlockState(&state)) return state; return EasyUnlockScreenlockStateHandler::NO_HARDLOCK; } bool EasyUnlockService::GetPersistedHardlockState( EasyUnlockScreenlockStateHandler::HardlockState* state) const { std::string user_id = GetUserEmail(); if (user_id.empty()) return false; PrefService* local_state = GetLocalState(); if (!local_state) return false; const base::DictionaryValue* dict = local_state->GetDictionary(prefs::kEasyUnlockHardlockState); int state_int; if (dict && dict->GetIntegerWithoutPathExpansion(user_id, &state_int)) { *state = static_cast<EasyUnlockScreenlockStateHandler::HardlockState>(state_int); return true; } return false; } void EasyUnlockService::ShowInitialUserState() { if (!GetScreenlockStateHandler()) return; EasyUnlockScreenlockStateHandler::HardlockState state; bool has_persisted_state = GetPersistedHardlockState(&state); if (!has_persisted_state) return; if (state == EasyUnlockScreenlockStateHandler::NO_HARDLOCK) { // Show connecting icon early when there is a persisted non hardlock state. UpdateScreenlockState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); } else { screenlock_state_handler_->MaybeShowHardlockUI(); } } EasyUnlockScreenlockStateHandler* EasyUnlockService::GetScreenlockStateHandler() { if (!IsAllowed()) return NULL; if (!screenlock_state_handler_) { screenlock_state_handler_.reset(new EasyUnlockScreenlockStateHandler( GetUserEmail(), GetHardlockState(), ScreenlockBridge::Get())); } return screenlock_state_handler_.get(); } bool EasyUnlockService::UpdateScreenlockState( EasyUnlockScreenlockStateHandler::State state) { EasyUnlockScreenlockStateHandler* handler = GetScreenlockStateHandler(); if (!handler) return false; handler->ChangeState(state); if (state != EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED) auth_attempt_.reset(); return true; } void EasyUnlockService::AttemptAuth(const std::string& user_id) { auth_attempt_.reset(new EasyUnlockAuthAttempt( profile_, GetUserEmail(), GetType() == TYPE_REGULAR ? EasyUnlockAuthAttempt::TYPE_UNLOCK : EasyUnlockAuthAttempt::TYPE_SIGNIN)); if (!auth_attempt_->Start(user_id)) auth_attempt_.reset(); } void EasyUnlockService::FinalizeUnlock(bool success) { if (auth_attempt_) auth_attempt_->FinalizeUnlock(GetUserEmail(), success); auth_attempt_.reset(); } void EasyUnlockService::FinalizeSignin(const std::string& key) { if (!auth_attempt_) return; std::string wrapped_secret = GetWrappedSecret(); if (!wrapped_secret.empty()) auth_attempt_->FinalizeSignin(GetUserEmail(), wrapped_secret, key); auth_attempt_.reset(); } void EasyUnlockService::CheckCryptohomeKeysAndMaybeHardlock() { #if defined(OS_CHROMEOS) std::string user_id = GetUserEmail(); if (user_id.empty()) return; const base::ListValue* device_list = GetRemoteDevices(); std::set<std::string> paired_devices; if (device_list) { chromeos::EasyUnlockDeviceKeyDataList parsed_paired; chromeos::EasyUnlockKeyManager::RemoteDeviceListToDeviceDataList( *device_list, &parsed_paired); for (const auto& device_key_data : parsed_paired) paired_devices.insert(device_key_data.psk); } if (paired_devices.empty()) { SetHardlockState(EasyUnlockScreenlockStateHandler::NO_PAIRING); return; } // No need to compare if a change is already recorded. if (GetHardlockState() == EasyUnlockScreenlockStateHandler::PAIRING_CHANGED || GetHardlockState() == EasyUnlockScreenlockStateHandler::PAIRING_ADDED) { return; } chromeos::EasyUnlockKeyManager* key_manager = chromeos::UserSessionManager::GetInstance()->GetEasyUnlockKeyManager(); DCHECK(key_manager); key_manager->GetDeviceDataList( chromeos::UserContext(user_id), base::Bind(&EasyUnlockService::OnCryptohomeKeysFetchedForChecking, weak_ptr_factory_.GetWeakPtr(), user_id, paired_devices)); #endif } void EasyUnlockService::SetTrialRun() { DCHECK(GetType() == TYPE_REGULAR); EasyUnlockScreenlockStateHandler* handler = GetScreenlockStateHandler(); if (handler) handler->SetTrialRun(); } void EasyUnlockService::AddObserver(EasyUnlockServiceObserver* observer) { observers_.AddObserver(observer); } void EasyUnlockService::RemoveObserver(EasyUnlockServiceObserver* observer) { observers_.RemoveObserver(observer); } void EasyUnlockService::Shutdown() { if (shut_down_) return; shut_down_ = true; ShutdownInternal(); weak_ptr_factory_.InvalidateWeakPtrs(); ResetScreenlockState(); bluetooth_detector_.reset(); #if defined(OS_CHROMEOS) power_monitor_.reset(); #endif } void EasyUnlockService::LoadApp() { DCHECK(IsAllowed()); #if defined(GOOGLE_CHROME_BUILD) base::FilePath easy_unlock_path; #if defined(OS_CHROMEOS) easy_unlock_path = base::FilePath("/usr/share/chromeos-assets/easy_unlock"); #endif // defined(OS_CHROMEOS) #ifndef NDEBUG // Only allow app path override switch for debug build. const CommandLine* command_line = CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kEasyUnlockAppPath)) { easy_unlock_path = command_line->GetSwitchValuePath(switches::kEasyUnlockAppPath); } #endif // !defined(NDEBUG) if (!easy_unlock_path.empty()) { extensions::ComponentLoader* loader = GetComponentLoader(profile_); if (!loader->Exists(extension_misc::kEasyUnlockAppId)) loader->Add(IDR_EASY_UNLOCK_MANIFEST, easy_unlock_path); ExtensionService* extension_service = extensions::ExtensionSystem::Get(profile_)->extension_service(); extension_service->EnableExtension(extension_misc::kEasyUnlockAppId); NotifyUserUpdated(); } #endif // defined(GOOGLE_CHROME_BUILD) } void EasyUnlockService::DisableAppIfLoaded() { extensions::ComponentLoader* loader = GetComponentLoader(profile_); if (!loader->Exists(extension_misc::kEasyUnlockAppId)) return; ExtensionService* extension_service = extensions::ExtensionSystem::Get(profile_)->extension_service(); extension_service->DisableExtension(extension_misc::kEasyUnlockAppId, extensions::Extension::DISABLE_RELOAD); } void EasyUnlockService::UnloadApp() { GetComponentLoader(profile_)->Remove(extension_misc::kEasyUnlockAppId); } void EasyUnlockService::ReloadApp() { // Make sure lock screen state set by the extension gets reset. ResetScreenlockState(); if (!GetComponentLoader(profile_)->Exists(extension_misc::kEasyUnlockAppId)) return; extensions::ExtensionSystem* extension_system = extensions::ExtensionSystem::Get(profile_); extension_system->extension_service()->ReloadExtension( extension_misc::kEasyUnlockAppId); NotifyUserUpdated(); } void EasyUnlockService::UpdateAppState() { if (IsAllowed()) { LoadApp(); #if defined(OS_CHROMEOS) if (!power_monitor_) power_monitor_.reset(new PowerMonitor(this)); #endif } else { bool bluetooth_waking_up = false; #if defined(OS_CHROMEOS) // If the service is not allowed due to bluetooth not being detected just // after system suspend is done, give bluetooth more time to be detected // before disabling the app (and resetting screenlock state). bluetooth_waking_up = power_monitor_.get() && power_monitor_->waking_up() && !bluetooth_detector_->IsPresent(); #endif if (!bluetooth_waking_up) { DisableAppIfLoaded(); ResetScreenlockState(); #if defined(OS_CHROMEOS) power_monitor_.reset(); #endif } } } void EasyUnlockService::NotifyUserUpdated() { std::string user_id = GetUserEmail(); if (user_id.empty()) return; // Notify the easy unlock app that the user info changed. extensions::api::easy_unlock_private::UserInfo info; info.user_id = user_id; info.logged_in = GetType() == TYPE_REGULAR; info.data_ready = info.logged_in || GetRemoteDevices() != NULL; scoped_ptr<base::ListValue> args(new base::ListValue()); args->Append(info.ToValue().release()); scoped_ptr<extensions::Event> event(new extensions::Event( extensions::api::easy_unlock_private::OnUserInfoUpdated::kEventName, args.Pass())); extensions::EventRouter::Get(profile_)->DispatchEventToExtension( extension_misc::kEasyUnlockAppId, event.Pass()); } void EasyUnlockService::NotifyTurnOffOperationStatusChanged() { FOR_EACH_OBSERVER( EasyUnlockServiceObserver, observers_, OnTurnOffOperationStatusChanged()); } void EasyUnlockService::ResetScreenlockState() { screenlock_state_handler_.reset(); auth_attempt_.reset(); } void EasyUnlockService::SetScreenlockHardlockedState( EasyUnlockScreenlockStateHandler::HardlockState state) { if (screenlock_state_handler_) screenlock_state_handler_->SetHardlockState(state); if (state != EasyUnlockScreenlockStateHandler::NO_HARDLOCK) auth_attempt_.reset(); } void EasyUnlockService::Initialize() { InitializeInternal(); #if defined(OS_CHROMEOS) // Only start Bluetooth detection for ChromeOS since the feature is // only offered on ChromeOS. Enabling this on non-ChromeOS platforms // previously introduced a performance regression: http://crbug.com/404482 // Make sure not to reintroduce a performance regression if re-enabling on // additional platforms. // TODO(xiyuan): Revisit when non-chromeos platforms are supported. bluetooth_detector_->Initialize(); #endif // defined(OS_CHROMEOS) } void EasyUnlockService::OnBluetoothAdapterPresentChanged() { UpdateAppState(); } void EasyUnlockService::SetHardlockStateForUser( const std::string& user_id, EasyUnlockScreenlockStateHandler::HardlockState state) { DCHECK(!user_id.empty()); PrefService* local_state = GetLocalState(); if (!local_state) return; DictionaryPrefUpdate update(local_state, prefs::kEasyUnlockHardlockState); update->SetIntegerWithoutPathExpansion(user_id, static_cast<int>(state)); if (GetUserEmail() == user_id) SetScreenlockHardlockedState(state); } #if defined(OS_CHROMEOS) void EasyUnlockService::OnCryptohomeKeysFetchedForChecking( const std::string& user_id, const std::set<std::string> paired_devices, bool success, const chromeos::EasyUnlockDeviceKeyDataList& key_data_list) { DCHECK(!user_id.empty() && !paired_devices.empty()); if (!success) { SetHardlockStateForUser(user_id, EasyUnlockScreenlockStateHandler::NO_PAIRING); return; } std::set<std::string> devices_in_cryptohome; for (const auto& device_key_data : key_data_list) devices_in_cryptohome.insert(device_key_data.psk); if (paired_devices != devices_in_cryptohome || GetHardlockState() == EasyUnlockScreenlockStateHandler::NO_PAIRING) { SetHardlockStateForUser( user_id, devices_in_cryptohome.empty() ? EasyUnlockScreenlockStateHandler::PAIRING_ADDED : EasyUnlockScreenlockStateHandler::PAIRING_CHANGED); } } #endif void EasyUnlockService::PrepareForSuspend() { DisableAppIfLoaded(); if (screenlock_state_handler_ && screenlock_state_handler_->IsActive()) { UpdateScreenlockState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); } }